diff --git a/build.gradle b/build.gradle index 3c16da85f..1c227ed6b 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-jdbc' runtimeOnly 'com.h2database:h2' + compileOnly 'org.projectlombok:lombok:1.18.30' + annotationProcessor 'org.projectlombok:lombok:1.18.30' } test { diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 649c9458e..1d228aca9 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -5,7 +5,7 @@ @Controller public class ReservationController { - private static final String VIEW_RESERVATION = "reservation"; + private static final String VIEW_RESERVATION = "new-reservation"; @GetMapping("/reservation") public String reservation() { diff --git a/src/main/java/roomescape/controller/TimeApiController.java b/src/main/java/roomescape/controller/TimeApiController.java new file mode 100644 index 000000000..3d39b3fd2 --- /dev/null +++ b/src/main/java/roomescape/controller/TimeApiController.java @@ -0,0 +1,37 @@ +package roomescape.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import roomescape.dto.TimeRequest; +import roomescape.dto.TimeResponse; +import roomescape.service.TimeService; + +import java.net.URI; +import java.util.List; + +@RestController +@RequestMapping("/times") +@RequiredArgsConstructor +public class TimeApiController { + + private final TimeService timeService; + + @PostMapping + public ResponseEntity create(@RequestBody TimeRequest request) { + TimeResponse response = timeService.create(request); + return ResponseEntity.created(URI.create("/times/" + response.getId())) + .body(response); + } + + @GetMapping + public List findAll() { + return timeService.findAll(); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable Long id) { + timeService.delete(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/roomescape/controller/TimeController.java b/src/main/java/roomescape/controller/TimeController.java new file mode 100644 index 000000000..1934146c1 --- /dev/null +++ b/src/main/java/roomescape/controller/TimeController.java @@ -0,0 +1,14 @@ +package roomescape.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class TimeController { + private static final String VIEW_TIME = "time"; + + @GetMapping("/time") + public String home() { + return VIEW_TIME; + } +} diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index 8cbf434b3..839986df0 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -10,9 +10,9 @@ public class Reservation { private final Long id; private final String name; private final LocalDate date; - private final LocalTime time; + private final Time time; - private Reservation(Long id, String name, LocalDate date, LocalTime time) { + private Reservation(Long id, String name, LocalDate date, Time time) { validate(name, date, time); this.id = id; this.name = name; @@ -20,11 +20,11 @@ private Reservation(Long id, String name, LocalDate date, LocalTime time) { this.time = time; } - public static Reservation of(Long id, String name, LocalDate date, LocalTime time) { + public static Reservation of(Long id, String name, LocalDate date, Time time) { return new Reservation(id, name, date, time); } - private void validate(String name, LocalDate date, LocalTime time) { + private void validate(String name, LocalDate date, Time time) { List errors = new ArrayList<>(); if (name == null || name.isBlank()) { @@ -54,7 +54,7 @@ public LocalDate getDate() { return date; } - public LocalTime getTime() { + public Time getTime() { return time; } } diff --git a/src/main/java/roomescape/domain/Time.java b/src/main/java/roomescape/domain/Time.java new file mode 100644 index 000000000..ff626db93 --- /dev/null +++ b/src/main/java/roomescape/domain/Time.java @@ -0,0 +1,38 @@ +package roomescape.domain; + +public class Time { + private final Long id; + private final String time; + + private Time(Long id, String time) { + validate(time); + this.id = id; + this.time = time; + } + + private void validate(String time) { + if (time == null) { + throw new IllegalArgumentException("시간 값은 null일 수 없습니다."); + } + if (time.isBlank()) { + throw new IllegalArgumentException("시간 값은 빈 문자열일 수 없습니다."); + } + } + + public static Time of(Long id, String time) { + return new Time(id, time); + } + + public static Time of(String time) { + return of(null, time); + } + + public Long getId() { + return id; + } + + public String getTime() { + return time; + } +} + diff --git a/src/main/java/roomescape/dto/ReservationRequest.java b/src/main/java/roomescape/dto/ReservationRequest.java index 8e4259418..144e739c4 100644 --- a/src/main/java/roomescape/dto/ReservationRequest.java +++ b/src/main/java/roomescape/dto/ReservationRequest.java @@ -16,9 +16,7 @@ public class ReservationRequest { @JsonFormat(pattern = "yyyy-MM-dd") private LocalDate date; - @NotNull - @JsonFormat(pattern = "HH:mm") - private LocalTime time; + private Long timeId; protected ReservationRequest() { } @@ -31,8 +29,7 @@ public LocalDate getDate() { return date; } - public LocalTime getTime() { - return time; + public Long getTimeId() { + return timeId; } } - diff --git a/src/main/java/roomescape/dto/ReservationResponse.java b/src/main/java/roomescape/dto/ReservationResponse.java index b5af322e2..bc5e7b671 100644 --- a/src/main/java/roomescape/dto/ReservationResponse.java +++ b/src/main/java/roomescape/dto/ReservationResponse.java @@ -14,13 +14,13 @@ public class ReservationResponse { private LocalDate date; @JsonFormat(pattern = "HH:mm") - private LocalTime time; + private TimeResponse time; public ReservationResponse(Reservation reservation) { this.id = reservation.getId(); this.name = reservation.getName(); this.date = reservation.getDate(); - this.time = reservation.getTime(); + this.time = new TimeResponse(reservation.getTime()); } public Long getId() { @@ -35,7 +35,7 @@ public LocalDate getDate() { return date; } - public LocalTime getTime() { + public TimeResponse getTime() { return time; } } \ No newline at end of file diff --git a/src/main/java/roomescape/dto/TimeRequest.java b/src/main/java/roomescape/dto/TimeRequest.java new file mode 100644 index 000000000..2067e2d52 --- /dev/null +++ b/src/main/java/roomescape/dto/TimeRequest.java @@ -0,0 +1,14 @@ +package roomescape.dto; + +import jakarta.validation.constraints.NotBlank; + +public class TimeRequest { + @NotBlank + private String time; + + protected TimeRequest() {}; + + public String getTime() { + return time; + } +} diff --git a/src/main/java/roomescape/dto/TimeResponse.java b/src/main/java/roomescape/dto/TimeResponse.java new file mode 100644 index 000000000..4db3a6343 --- /dev/null +++ b/src/main/java/roomescape/dto/TimeResponse.java @@ -0,0 +1,21 @@ +package roomescape.dto; + +import roomescape.domain.Time; + +public class TimeResponse { + private Long id; + private String time; + + public TimeResponse(Time time) { + this.id = time.getId(); + this.time = time.getTime(); + } + + public Long getId() { + return id; + } + + public String getTime() { + return time; + } +} diff --git a/src/main/java/roomescape/repository/JdbcReservationRepository.java b/src/main/java/roomescape/repository/JdbcReservationRepository.java index 5d950e1e8..49d069ebf 100644 --- a/src/main/java/roomescape/repository/JdbcReservationRepository.java +++ b/src/main/java/roomescape/repository/JdbcReservationRepository.java @@ -1,5 +1,7 @@ package roomescape.repository; +import java.sql.PreparedStatement; +import java.time.LocalDate; import java.util.List; import java.util.Map; import java.util.Optional; @@ -7,8 +9,11 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import roomescape.domain.Reservation; +import roomescape.domain.Time; import roomescape.dto.ReservationResponse; @Repository @@ -24,34 +29,60 @@ public JdbcReservationRepository(DataSource dataSource) { .usingGeneratedKeyColumns("id"); } - private final RowMapper rowMapper = (rs, rowNum) -> Reservation.of( - rs.getLong("id"), - rs.getString("name"), - rs.getDate("date").toLocalDate(), - rs.getTime("time").toLocalTime() - ); + private final RowMapper rowMapper = (rs, rowNum) -> { + Long reservationId = rs.getLong("reservation_id"); + String name = rs.getString("name"); + LocalDate date = LocalDate.parse(rs.getString("date")); + + Long timeId = rs.getLong("time_id"); + String timeValue = rs.getString("time_value"); + Time time = Time.of(timeId, timeValue); + + return Reservation.of(reservationId, name, date, time); + }; @Override public List findAll() { - List reservations = jdbcTemplate.query("SELECT * FROM reservation ORDER BY id", rowMapper); + String sql = """ + SELECT + r.id AS reservation_id, + r.name, + r.date, + t.id AS time_id, + t.time AS time_value + FROM reservation r + INNER JOIN time t ON r.time_id = t.id + ORDER BY r.id + """; + + List reservations = jdbcTemplate.query(sql, rowMapper); return reservations.stream() .map(ReservationResponse::new) .toList(); } + @Override public ReservationResponse save(Reservation reservation) { - Number key = insert.executeAndReturnKey(Map.of( - "name", reservation.getName(), - "date", reservation.getDate(), - "time", reservation.getTime() - )); + String sql = "INSERT INTO reservation (name, date, time_id) VALUES (?, ?, ?)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + + jdbcTemplate.update(con -> { + PreparedStatement ps = con.prepareStatement(sql, new String[]{"id"}); + ps.setString(1, reservation.getName()); + ps.setString(2, reservation.getDate().toString()); + ps.setLong(3, reservation.getTime().getId()); + return ps; + }, keyHolder); + + Long generatedId = keyHolder.getKey().longValue(); Reservation saved = Reservation.of( - key.longValue(), + generatedId, reservation.getName(), reservation.getDate(), reservation.getTime() ); + return new ReservationResponse(saved); } diff --git a/src/main/java/roomescape/repository/JdbcTimeRepository.java b/src/main/java/roomescape/repository/JdbcTimeRepository.java new file mode 100644 index 000000000..9910a5a88 --- /dev/null +++ b/src/main/java/roomescape/repository/JdbcTimeRepository.java @@ -0,0 +1,54 @@ +package roomescape.repository; + +import java.sql.PreparedStatement; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import roomescape.domain.Time; + +@Repository +@RequiredArgsConstructor +public class JdbcTimeRepository implements TimeRepository { + + private final JdbcTemplate jdbcTemplate; + + private final RowMapper