Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
12 commits
Select commit Hold shift + click to select a range
68c3ece
fix: build.gradle ์„ค์ •์— jpa๋กœ ์ˆ˜์ •
ParkJiYeoung8297 Jun 18, 2026
f5c94fd
refactor: Time๊ณผ Theme์˜ ์˜์†์„ฑ ๊ด€๋ฆฌ๋ฅผ JPA๋กœ ์ „ํ™˜
ParkJiYeoung8297 Jun 19, 2026
aa64f6d
test: Time์— ๋Œ€ํ•œ mockMvcTest ์ถ”๊ฐ€
ParkJiYeoung8297 Jun 19, 2026
d921da3
feat: ReservationSlot๊ณผ Reservation์˜ ์˜์†์„ฑ ๊ด€๋ฆฌ๋ฅผ JPA๋กœ ์ „ํ™˜
ParkJiYeoung8297 Jun 21, 2026
3d2c6c6
refactor: Reservation ๋„๋ฉ”์ธ ์ˆ˜์ •์— ๋”ฐ๋ฅธ ํ…Œ์ŠคํŠธ ์ˆ˜์ •
ParkJiYeoung8297 Jun 21, 2026
1300bf0
fix: Reservation๊ณผ ReservationSlot์„ ์–‘๋ฐฉํ–ฅ ๋งคํ•‘์œผ๋กœ ์ˆ˜์ •
ParkJiYeoung8297 Jun 21, 2026
924da2a
fix: ๋„๋ฉ”์ธ ์ˆ˜์ •์— ๋”ฐ๋ฅธ ํ…Œ์ŠคํŠธ ์ˆ˜์ •
ParkJiYeoung8297 Jun 21, 2026
8b85452
docs: Readme update [1๋‹จ๊ณ„ - JPA ์ „ํ™˜]
ParkJiYeoung8297 Jun 21, 2026
01bfb64
feat: ์‚ฌ์šฉ์ž ์ด๋ฆ„์œผ๋กœ ์˜ˆ์•ฝ ์กฐํšŒ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
ParkJiYeoung8297 Jun 22, 2026
d39bc37
chore: ์ฃผ์„ ํ•ด์ œ
ParkJiYeoung8297 Jun 22, 2026
2948d6f
fix: ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ์˜ค๋ฅ˜ ์ˆ˜์ •
ParkJiYeoung8297 Jun 22, 2026
1b51c0b
docs: Readme update
ParkJiYeoung8297 Jun 23, 2026
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
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ 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-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'

implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
Expand Down
48 changes: 48 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,51 @@ ReservationSlot ํ•˜๋‚˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ด€๋ฆฌ
1. ๋ชจ๋‹ˆํ„ฐ๋ง
https://github.com/woowacourse/spring-roomescape-waiting/pull/510#discussion_r3393603770

------

# ์„ ํƒ ๋ฏธ์…˜
### 1๋‹จ๊ณ„ - JPA ์ „ํ™˜
- [x] build.gradle ์„ค์ •์— jpa๋กœ ์ˆ˜์ •
- [x] Time๊ณผ Theme์˜ ์˜์†์„ฑ ๊ด€๋ฆฌ๋ฅผ JPA๋กœ ์ „ํ™˜
- [x] ReservationSlot๊ณผ Reservation์˜ ์˜์†์„ฑ ๊ด€๋ฆฌ๋ฅผ JPA๋กœ ์ „ํ™˜
- [x] Reservation๊ณผ ReservationSlot์„ ์–‘๋ฐฉํ–ฅ ๋งคํ•‘์œผ๋กœ ์ˆ˜์ •

### 2๋‹จ๊ณ„: ๋‚ด ์˜ˆ์•ฝ ๋ชฉ๋ก ์กฐํšŒ ๊ธฐ๋Šฅ
- [x] ์‚ฌ์šฉ์ž ์ด๋ฆ„์œผ๋กœ ์˜ˆ์•ฝ ์กฐํšŒ ๊ธฐ๋Šฅ ์ถ”๊ฐ€

### 3๋‹จ๊ณ„: ์˜ˆ์•ฝ ๋Œ€๊ธฐ ๊ธฐ๋Šฅ
์ด๋ฏธ ๊ตฌํ˜„๋˜์–ด์žˆ๋Š” ๋Œ€๊ธฐ ๊ธฐ๋Šฅ์ด๋ผ, ์ด์ „ ๋ฏธ์…˜๊ณผ ์ด๋ฒˆ ๋ฏธ์…˜์˜ SQL๋ฌธ ๋กœ๊ทธ ๋น„๊ต์— ์ค‘์ ์„ ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.

### 4๋‹จ๊ณ„ - ์˜ˆ์•ฝ ๋Œ€๊ธฐ ๊ด€๋ฆฌ
- [x] ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ์˜ค๋ฅ˜ ์ˆ˜์ •

### ๋ฐœํ–‰ SQL ๋ฐœ์ทŒ
JDBC์—์„œ๋Š” ์ œ๊ฐ€ ์ง์ ‘ SQL์„ ์ž‘์„ฑํ–ˆ๋˜ ๋ฐ˜๋ฉด, JPA์—์„œ๋Š” hibernate๊ฐ€ ์—”ํ‹ฐํ‹ฐ ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•ด์„œ Insert SQL ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ JDBC๋Š” ์‹คํ–‰๊ณผ์ •(SQL ์ƒ์„ฑ, ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ, Generated Key ์กฐํšŒ ๊ณผ์ • ๋“ฑ)์ด ๋กœ๊ทธ๋กœ ์ž˜ ๋“œ๋Ÿฌ๋‚˜๋Š” ๋ฐ˜๋ฉด, JPA์—์„œ๋Š” hibernate ๋‚ด๋ถ€์—์„œ ์ˆ˜ํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒ๋Œ€์ ์œผ๋กœ ๋‹จ์ˆœํ•œ SQL ๋กœ๊ทธ๋งŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ, JDBC์—์„œ๋Š” DB์—์„œ ์ƒ์„ฑํ•œ primary key๋„ ๊ฐœ๋ฐœ์ž๊ฐ€ SimpleJdbcInsert๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์กฐํšŒํ•˜๊ณ  ๊ฐ์ฒด์— ๋ฐ˜์˜ํ•ด์•ผํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด JPA์—์„œ๋Š” Hibernate๊ฐ€ Insert ํ›„์— ์ƒ์„ฑ๋œ primary key๋ฅผ ์กฐํšŒํ•ด์„œ ์ž๋™์œผ๋กœ ์ฃผ์ž…ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์ฒ˜๋ฆฌํ•  ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.

```
[JDBC]
2026-06-22T14:21:43.181+09:00 DEBUG 9599 --- [ Test worker] o.s.jdbc.core.simple.SimpleJdbcInsert : Compiled insert object: insert string is [INSERT INTO reservation (NAME, RESERVATION_SLOT_ID, STATUS, UPDATED_AT) VALUES(?, ?, ?, ?)]
2026-06-22T14:21:43.182+09:00 DEBUG 9599 --- [ Test worker] o.s.jdbc.core.simple.SimpleJdbcInsert : JdbcInsert for table [reservation] compiled
2026-06-22T14:21:43.182+09:00 DEBUG 9599 --- [ Test worker] o.s.jdbc.core.simple.SimpleJdbcInsert : The following parameters are used for call INSERT INTO reservation (NAME, RESERVATION_SLOT_ID, STATUS, UPDATED_AT) VALUES(?, ?, ?, ?) with: [๊น€์ฒ ์ˆ˜, 23, RESERVED, 2026-06-22T14:21:43.136685]
2026-06-22T14:21:43.182+09:00 DEBUG 9599 --- [ Test worker] o.s.jdbc.core.JdbcTemplate : Executing SQL update and returning generated keys
2026-06-22T14:21:43.182+09:00 DEBUG 9599 --- [ Test worker] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement
2026-06-22T14:21:43.182+09:00 DEBUG 9599 --- [ Test worker] o.s.jdbc.core.simple.SimpleJdbcInsert : Using generated keys support with array of column names.

[JPA]
Hibernate:
insert
into
reservation
(name, reservation_slot_id, status, updated_at, id)
values
(?, ?, ?, ?, default)
```

### ๋ณ€๊ฒฝ์‚ฌํ•ญ
JDBC์—์„œ JPA๋กœ ๋„˜์–ด์˜ค๋ฉฐ ์ƒ๊ธด ๋ณ€๊ฒฝ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.
1)`@Entity` , `@id`, `@GeneratedValue` ์ถ”๊ฐ€
2)๋„๋ฉ”์ธ ํ•„๋“œ์—์„œ final์ œ๊ฑฐ (JPA์—์„œ๋Š” EntityManger๊ฐ€ ๊ฐ์ฒด ํ•„๋“œ๊ฐ’์„ ์ฑ„์›Œ์ฃผ๊ณ  ์ˆ˜์ •ํ•˜๊ธฐ ๋•Œ๋ฌธ์— final ๋ถˆ๊ฐ€๋Šฅ)
3)protected ์ƒ์„ฑ์ž ์ถ”๊ฐ€(jpa์ „์šฉ), ๊ธฐ์กด ์‚ฌ์šฉ์ž ์ง€์ •์—์„œ๋Š” ์ƒ์„ฑ์ž ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ id ์‚ญ์ œ(์ด๊ฒƒ ์—ญ์‹œ JPA๊ฐ€ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— id๋Š” ์ง์ ‘ ์ง€์ • ์•ˆํ•จ)
4)JdbcTemplate์™€ RowMapper ์‚ญ์ œ
5)JpaRepository<T, ID>๋ฅผ extendsํ•˜์—ฌ ์ œ๊ณต๋œ CRUD ๊ธฐ๋Šฅ ์‚ฌ์šฉ
56 changes: 33 additions & 23 deletions src/main/java/roomescape/domain/Reservation.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
package roomescape.domain;

import java.time.LocalDateTime;
import java.util.Objects;

import jakarta.persistence.*;
import roomescape.exception.CustomException;
import roomescape.exception.ErrorCode;

@Entity
@Table(name = "reservation")
public class Reservation {
private final Long id;
private final String name;
private long reservationSlotId;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne
@JoinColumn(name = "reservation_slot_id")
private ReservationSlot reservationSlot;

@Column(nullable = false)
private String name;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Status status;

@Column(name = "updated_at", nullable = false)
private LocalDateTime updateAt;

public Reservation(Long id, String name, Long reservationSlotId, Status status, LocalDateTime updateAt) {
protected Reservation(){}

public Reservation(ReservationSlot reservationSlot, String name, Status status, LocalDateTime updateAt) {
validateName(name);
this.id = id;
this.reservationSlot = reservationSlot;
this.name = name;
this.reservationSlotId = reservationSlotId;
this.status = status;
this.updateAt = updateAt;
}
Expand All @@ -41,10 +56,17 @@ public void promote() {
this.status = Status.RESERVED;
}

public void update(LocalDateTime now, long reservationSlotId, Status status) {
public void update(LocalDateTime now, Status status) {
validateUpdateAt(now);
validateNotCanceledStatus();
this.reservationSlotId = reservationSlotId;
this.status = status;
this.updateAt = now;
}

public void moveTo(ReservationSlot reservationSlot, LocalDateTime now, Status status) {
validateUpdateAt(now);
validateNotCanceledStatus();
this.reservationSlot = reservationSlot;
this.status = status;
this.updateAt = now;
}
Expand Down Expand Up @@ -82,10 +104,6 @@ public String getName() {
return name;
}

public long getReservationSlotId() {
return reservationSlotId;
}

public Status getStatus() {
return status;
}
Expand All @@ -94,15 +112,7 @@ public LocalDateTime getUpdateAt() {
return updateAt;
}

@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Reservation that = (Reservation) o;
return reservationSlotId == that.reservationSlotId && Objects.equals(name, that.name);
}

@Override
public int hashCode() {
return Objects.hash(name, reservationSlotId);
public ReservationSlot getReservationSlot() {
return reservationSlot;
}
}
80 changes: 64 additions & 16 deletions src/main/java/roomescape/domain/ReservationSlot.java
Original file line number Diff line number Diff line change
@@ -1,52 +1,84 @@
package roomescape.domain;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import jakarta.persistence.*;
import roomescape.domain.vo.ReservationDeletion;
import roomescape.domain.vo.ReservationSlotInfo;
import roomescape.domain.vo.ReservationUpdate;
import roomescape.exception.CustomException;
import roomescape.exception.ErrorCode;

@Entity
@Table(
name = "reservation_slot",
uniqueConstraints = @UniqueConstraint(columnNames = {"date", "time_id", "theme_id"})
)
public class ReservationSlot {
private final ReservationSlotInfo slot;
private final List<Reservation> reservations;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

public ReservationSlot(ReservationSlotInfo slot, List<Reservation> reservations) {
this.slot = slot;
@Column(nullable = false)
private LocalDate date;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "time_id", nullable = false)
private Time time;

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

@OneToMany(mappedBy = "reservationSlot", cascade = CascadeType.PERSIST)
@OrderBy("updateAt ASC, id ASC")
private List<Reservation> reservations = new ArrayList<>();

protected ReservationSlot() {
}

public ReservationSlot(
LocalDate date,
Time time,
Theme theme,
List<Reservation> reservations
) {
this.date = date;
this.time = time;
this.theme = theme;
this.reservations = new ArrayList<>(reservations);
}

public Reservation reserve(String name, LocalDateTime now) {
validateUniqueReservation(name);
validateNotPastReservation(now, ErrorCode.PAST_DATE_RESERVATION);

Reservation newReservation = new Reservation(null, name, slot.slotId(), calculateStatus(), now);
reservations.add(newReservation);
return newReservation;
Reservation reservation = new Reservation(this, name, calculateStatus(), now);
reservations.add(reservation);
return reservation;
}

public Reservation moveIn(Reservation reservation, String name, LocalDateTime now) {
validateReservationOwner(reservation, name);
validateUniqueReservation(name);
validateNotPastReservation(now, ErrorCode.UNALLOWED_UPDATE_PAST_RESERVATION);

reservation.update(now, slot.slotId(), calculateStatus());
reservation.moveTo(this, now, calculateStatus());
reservations.add(reservation);
return reservation;
}

public ReservationUpdate moveOut(long reservationId, String name, LocalDateTime now) {
public Reservation moveOut(long reservationId, String name, LocalDateTime now) {
Reservation reservation = findReservation(reservationId);
validateReservationOwner(reservation, name);
validateNotPastReservation(now, ErrorCode.UNALLOWED_UPDATE_PAST_RESERVATION);

Optional<Reservation> promotedReservation = promoteReservation(reservation);
promoteReservation(reservation);
reservations.remove(reservation);
return new ReservationUpdate(reservation,promotedReservation);
return reservation;
}

private Status calculateStatus() {
Expand Down Expand Up @@ -100,7 +132,7 @@ private Reservation findReservation(long reservationId) {
}

private void validateNotPastReservation(LocalDateTime now, ErrorCode code) {
LocalDateTime reservationTime = LocalDateTime.of(slot.date(), slot.time().getStartAt());
LocalDateTime reservationTime = LocalDateTime.of(date, time.getStartAt());
if (reservationTime.isBefore(now)) {
throw new CustomException(code);
}
Expand All @@ -121,7 +153,23 @@ private void validateUniqueReservation(String name) {
}
}

public ReservationSlotInfo getSlot() {
return slot;
public Long getId() {
return id;
}

public LocalDate getDate() {
return date;
}

public Time getTime() {
return time;
}

public Theme getTheme() {
return theme;
}

public List<Reservation> getReservations() {
return Collections.unmodifiableList(reservations);
}
}
31 changes: 27 additions & 4 deletions src/main/java/roomescape/domain/Theme.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,37 @@

import java.util.Objects;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import roomescape.exception.CustomException;
import roomescape.exception.ErrorCode;

@Entity
public class Theme {
private final Long id;
private final String name;
private final String description;
private final String thumbnailUrl;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private String thumbnailUrl;

// JPA์šฉ
protected Theme() {
}

// ์‚ฌ์šฉ์ž ์ƒ์„ฑ
public Theme(String name, String description, String thumbnailUrl) {
validateNameLength(name);
validateDescriptionLength(description);
validateThumbnailUrlLength(thumbnailUrl);
this.name = name;
this.description = description;
this.thumbnailUrl = thumbnailUrl;
}

// JDBC โ†’ JPA ๊ณผ์ •์—์„œ ๊นจ์ง€๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ๋กœ ๊ณง ์‚ญ์ œ ์˜ˆ์ •
public Theme(Long id, String name, String description, String thumbnailUrl) {
validateNameLength(name);
validateDescriptionLength(description);
Expand All @@ -21,6 +43,7 @@ public Theme(Long id, String name, String description, String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
}


private void validateNameLength(String name) {
if (name.length() > 255) {
throw new CustomException(ErrorCode.THEME_NAME_TOO_LONG);
Expand Down
18 changes: 16 additions & 2 deletions src/main/java/roomescape/domain/Time.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
package roomescape.domain;

import jakarta.persistence.*;

import java.time.LocalTime;

@Entity
@Table(name = "reservation_time")
public class Time {
private final Long id;
private final LocalTime startAt;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private LocalTime startAt;

protected Time(){}

public Time(LocalTime startAt) {
this.startAt = startAt;
}

// JDBC โ†’ JPA ๊ณผ์ •์—์„œ ๊นจ์ง€๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ๋กœ ๊ณง ์‚ญ์ œ ์˜ˆ์ •
public Time(Long id, LocalTime startAt) {
this.id = id;
this.startAt = startAt;
Expand All @@ -19,3 +32,4 @@ public LocalTime getStartAt() {
return startAt;
}
}

This file was deleted.

Loading