Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# λ°©νƒˆμΆœ μ˜ˆμ•½ λŒ€κΈ° λ―Έμ…˜
# JPA λ°©νƒˆμΆœ μ˜ˆμ•½ λŒ€κΈ°

## κΈ°λŠ₯ λͺ…μ„Έ

- [x] `reservation_waiting` λ°˜μ •κ·œν™” β€” `reservation` 직접 μ°Έμ‘° 제거, `date` / `theme_id` / `time_id` 직접 보유
- [x] μ˜ˆμ•½ μ·¨μ†Œ μ‹œ 첫 번째 λŒ€κΈ°μžλ₯Ό μžλ™μœΌλ‘œ μ˜ˆμ•½μœΌλ‘œ 승격
- [x] μ·¨μ†Œ λŒ€μƒ μ˜ˆμ•½μ˜ λŒ€κΈ° 쑴재 μ—¬λΆ€ 확인
- [x] 첫 번째 λŒ€κΈ°μžλ₯Ό μ‹ κ·œ μ˜ˆμ•½μœΌλ‘œ 등둝
- [x] 승격된 λŒ€κΈ° 데이터 μ‚­μ œ
- [x] λŒ€κΈ° μˆœλ²ˆμ€ `requested_at` μ˜€λ¦„μ°¨μˆœμœΌλ‘œ κ²°μ •λ˜λ©° 승격 ν›„ μžλ™ μž¬μ •λ ¬
- [x] μ·¨μ†ŒΒ·μŠΉκ²© κ³Όμ • 쀑 μ‹€νŒ¨ μ‹œ νŠΈλžœμž­μ…˜ 둀백으둜 데이터 일관성 보μž₯
1단계
- [X] λ§€ν•‘ λ³€ν™˜
- [X] Theme λ§€ν•‘ λ³€ν™˜
- [X] ReservationTime λ§€ν•‘ λ³€ν™˜
- [ ] 연관관계 λ§€ν•‘
- [X] Reservation λ§€ν•‘
- [ ] ReservationWaiting λ§€ν•‘

## API λͺ…μ„Έ

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-validation'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,34 @@
import roomescape.exception.ErrorCode;
import roomescape.reservation.Reservation;
import roomescape.reservation.service.ReservationService;
import roomescape.reservationtime.ReservationTime;
import roomescape.reservationtime.service.ReservationTimeService;
import roomescape.reservationwaiting.ReservationWaiting;
import roomescape.reservationwaiting.service.ReservationWaitingService;
import roomescape.theme.Theme;
import roomescape.theme.service.ThemeService;

@Service
public class ReservationApplicationService {
private static final Logger log = LoggerFactory.getLogger(ReservationApplicationService.class);
private final ReservationService reservationService;
private final ReservationWaitingService reservationWaitingService;
private final WaitingPromotionService waitingPromotionService;
private final ThemeService themeService;
private final ReservationTimeService reservationTimeService;

public ReservationApplicationService(
final ReservationService reservationService,
final ReservationWaitingService reservationWaitingService,
final WaitingPromotionService waitingPromotionService
final WaitingPromotionService waitingPromotionService,
final ThemeService themeService,
final ReservationTimeService reservationTimeService
) {
this.reservationService = reservationService;
this. reservationWaitingService = reservationWaitingService;
this.waitingPromotionService = waitingPromotionService;
this.themeService = themeService;
this.reservationTimeService = reservationTimeService;
}

@Transactional
Expand All @@ -35,7 +45,10 @@ public ReservationWaiting saveWaiting(String name, LocalDate date, Long themeId,
Reservation reservation = reservationService.findByDateAndThemeIdAndTimeId(date, themeId, timeId);
validateReservationOwner(name, reservation);

return reservationWaitingService.save(name, date, themeId, timeId);
Theme theme = themeService.getById(themeId);
ReservationTime time = reservationTimeService.getById(timeId);

return reservationWaitingService.save(name, date, theme, time);
}

public void cancelReservation(Long id, String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import roomescape.history.MyHistory;
import roomescape.history.repository.MyHistoryRepository;

Expand All @@ -14,6 +15,7 @@ public MyHistoryService(final MyHistoryRepository myHistoryRepository) {
this.myHistoryRepository = myHistoryRepository;
}

@Transactional
public List<MyHistory> getAllByName(final String name) {
return myHistoryRepository.findByUserName(name);
}
Expand Down
47 changes: 42 additions & 5 deletions src/main/java/roomescape/reservation/Reservation.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,54 @@
package roomescape.reservation;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;
import roomescape.reservationtime.ReservationTime;
import roomescape.theme.Theme;

@Entity
@Table(name = "reservation",
uniqueConstraints = {
@UniqueConstraint(
columnNames = {
"date",
"theme_id",
"time_id"
}
),
}
)
public class Reservation {
private final Long id;
private final String name;
private final LocalDate date;
private final Theme theme;
private final ReservationTime time;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String name;

@Column(nullable = false)
private LocalDate date;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "theme_id")
private Theme theme;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "time_id")
private ReservationTime time;

protected Reservation() {}

private Reservation(final Long id, final String name, final LocalDate date, final Theme theme, final ReservationTime time) {
validate(name, date, theme, time);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package roomescape.reservation.repository;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import roomescape.reservation.Reservation;

public interface JpaReservationRepository extends JpaRepository<Reservation, Long> {
Optional<Reservation> findByIdAndName(long id, String name);

Optional<Reservation> findByDateAndThemeIdAndTimeId(LocalDate date, long themeId, long timeId);

boolean existsByTimeId(long timeId);

boolean existsByThemeId(long themeId);

// μ˜ˆμ•½μ΄ 생성 μ‹œ 쀑볡 μ˜ˆμ•½μΈμ§€ 확인
boolean existsByDateAndThemeIdAndTimeId(LocalDate date, long themeId, long timeId);

// μ˜ˆμ•½ μˆ˜μ • μ‹œ 쀑볡 μ˜ˆμ•½μ΄ λ˜λŠ”μ§€ 확인 (λ‚΄ μ˜ˆμ•½ 좩돌 제거)
boolean existsByDateAndThemeIdAndTimeIdAndIdNot(LocalDate date, long themeId, long timeId, long reservationId);

@Query("SELECT r.time.id FROM Reservation r WHERE r.date = :date AND r.theme.id = :themeId")
List<Long> findReservedTimeIdsByDateAndThemeId(@Param("date") LocalDate date, @Param("themeId") long themeId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,37 @@
import roomescape.exception.ResourceNotFoundException;
import roomescape.reservation.Reservation;
import roomescape.reservation.ReservationValidator;
import roomescape.reservation.repository.ReservationRepository;
import roomescape.reservation.repository.JpaReservationRepository;
import roomescape.reservationtime.ReservationTime;
import roomescape.reservationtime.service.ReservationTimeService;
import roomescape.theme.Theme;
import roomescape.theme.service.ThemeService;

@Service
@Transactional(readOnly = true)
public class ReservationService {
private final ReservationRepository reservationRepository;
private final JpaReservationRepository jpaReservationRepository;
private final ReservationTimeService reservationTimeService;
private final ThemeService themeService;
private final ReservationValidator reservationValidator;

public ReservationService(
final ReservationRepository reservationRepository,
final JpaReservationRepository jpaReservationRepository,
final ReservationTimeService reservationTimeService,
final ThemeService themeService,
final ReservationValidator reservationValidator
) {
this.reservationRepository = reservationRepository;
this.jpaReservationRepository = jpaReservationRepository;
this.reservationTimeService = reservationTimeService;
this.themeService = themeService;
this.reservationValidator = reservationValidator;
}

public List<Reservation> getAll() {
return reservationRepository.findAll();
return jpaReservationRepository.findAll();
}

@Transactional
public Reservation save(final String name, final LocalDate date, final Long themeId, final Long timeId) {
reservationValidator.validateCreateReferenceIds(themeId, timeId);

Expand All @@ -46,56 +48,60 @@ public Reservation save(final String name, final LocalDate date, final Long them
Reservation nonIdReservation = Reservation.createNew(name, date, theme, reservationTime);
reservationValidator.validateReservable(nonIdReservation);

if(reservationRepository.existsByDateAndThemeIdAndTimeId(date, themeId, timeId)){
if(jpaReservationRepository.existsByDateAndThemeIdAndTimeId(date, themeId, timeId)){
throw new ConflictException(ErrorCode.RESERVATION_DUPLICATED, "λ™μΌν•œ μ‹œκΈ°μ— μ˜ˆμ•½μ„ ν•  수 μ—†μŠ΅λ‹ˆλ‹€.");
}

return reservationRepository.save(nonIdReservation);
return jpaReservationRepository.save(nonIdReservation);
}


@Transactional
public void saveWith(String name, LocalDate date, Theme theme, ReservationTime reservationTime) {
Reservation nonIdReservation = Reservation.createNew(name, date, theme, reservationTime);

reservationRepository.save(nonIdReservation);
jpaReservationRepository.save(nonIdReservation);
}

public Reservation findByDateAndThemeIdAndTimeId(final LocalDate date, final Long themeId, final Long timeId) {
return reservationRepository.findByDateAndThemeIdAndTimeId(date, themeId, timeId)
return jpaReservationRepository.findByDateAndThemeIdAndTimeId(date, themeId, timeId)
.orElseThrow(() -> new ResourceNotFoundException(
ErrorCode.RESERVATION_NOT_FOUND,
"ν•΄λ‹Ή μ˜ˆμ•½μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."
));
}

@Transactional
public void deleteById(final long id) {
int affectedRowCount = reservationRepository.deleteById(id);

if(affectedRowCount <= 0) {
if (!jpaReservationRepository.existsById(id)){
throw new ResourceNotFoundException(ErrorCode.RESERVATION_NOT_FOUND, "μ‚­μ œλœ μ˜ˆμ•½ 데이터가 μ—†μŠ΅λ‹ˆλ‹€.");
}

jpaReservationRepository.deleteById(id);
}

@Transactional
public Reservation deleteByIdAndName(final long id, final String name) {
reservationValidator.validateLookupName(name);

Reservation reservation = reservationRepository.findByIdAndName(id, name)
Reservation reservation = jpaReservationRepository.findByIdAndName(id, name)
.orElseThrow(() -> new ResourceNotFoundException(
ErrorCode.MY_RESERVATION_NOT_FOUND,
"μ‘°νšŒν•œ μ΄λ¦„μœΌλ‘œ 찾은 μ˜ˆμ•½μ΄ μ—†μŠ΅λ‹ˆλ‹€."
));

reservationValidator.validateCancelable(reservation);

int affectedRowCount = reservationRepository.deleteById(reservation.getId());

if(affectedRowCount <= 0) {
if (!jpaReservationRepository.existsById(id)) {
throw new ResourceNotFoundException(ErrorCode.RESERVATION_NOT_FOUND, "ν•΄λ‹Ή μ˜ˆμ•½ 데이터가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.");
}

jpaReservationRepository.deleteById(reservation.getId());

return reservation;
}

@Transactional
public Reservation updateByIdAndName(
final long id,
final String name,
Expand All @@ -105,7 +111,7 @@ public Reservation updateByIdAndName(
reservationValidator.validateLookupName(name);
reservationValidator.validateUpdateReferenceIds(timeId);

Reservation reservation = reservationRepository.findByIdAndName(id, name)
Reservation reservation = jpaReservationRepository.findByIdAndName(id, name)
.orElseThrow(() -> new ResourceNotFoundException(
ErrorCode.MY_RESERVATION_NOT_FOUND,
"μ‘°νšŒν•œ μ΄λ¦„μœΌλ‘œ 찾은 μ˜ˆμ•½μ΄ μ—†μŠ΅λ‹ˆλ‹€."
Expand All @@ -117,7 +123,7 @@ public Reservation updateByIdAndName(
Reservation updatedReservation = reservation.withDateAndTime(date, reservationTime);
reservationValidator.validateReservable(updatedReservation);

if (reservationRepository.existsByDateAndThemeIdAndTimeIdExcludingId(
if (jpaReservationRepository.existsByDateAndThemeIdAndTimeIdAndIdNot(
date,
reservation.getTheme().getId(),
timeId,
Expand All @@ -126,6 +132,6 @@ public Reservation updateByIdAndName(
throw new ConflictException(ErrorCode.RESERVATION_DUPLICATED, "λ™μΌν•œ μ‹œκΈ°μ— μ˜ˆμ•½μ„ ν•  수 μ—†μŠ΅λ‹ˆλ‹€.");
}

return reservationRepository.update(updatedReservation);
return jpaReservationRepository.save(updatedReservation);
}
}
15 changes: 13 additions & 2 deletions src/main/java/roomescape/reservationtime/ReservationTime.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package roomescape.reservationtime;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.time.LocalTime;
import java.util.Objects;

@Entity
public class ReservationTime {
private final Long id;
private final LocalTime startAt;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private LocalTime startAt;

protected ReservationTime() {}

private ReservationTime(final Long id, final LocalTime startAt) {
validateStartAt(startAt);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package roomescape.reservationtime.repository;

import java.time.LocalTime;
import org.springframework.data.jpa.repository.JpaRepository;
import roomescape.reservationtime.ReservationTime;

public interface JpaReservationTimeRepository extends JpaRepository<ReservationTime, Long> {

boolean existsByStartAt(LocalTime startAt);
}
Loading