diff --git a/build.gradle b/build.gradle index 1be9887e7..454d27036 100644 --- a/build.gradle +++ b/build.gradle @@ -16,11 +16,14 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' developmentOnly 'org.springframework.boot:spring-boot-devtools' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' + + runtimeOnly 'com.h2database:h2' } test { diff --git a/src/main/java/roomescape/HomeController.java b/src/main/java/roomescape/HomeController.java deleted file mode 100644 index 6c5a65293..000000000 --- a/src/main/java/roomescape/HomeController.java +++ /dev/null @@ -1,12 +0,0 @@ -package roomescape; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -public class HomeController { - @GetMapping("/") - public String home() { - return "home"; - } -} diff --git a/src/main/java/roomescape/Reservation.java b/src/main/java/roomescape/Reservation.java deleted file mode 100644 index bbc44073e..000000000 --- a/src/main/java/roomescape/Reservation.java +++ /dev/null @@ -1,4 +0,0 @@ -package roomescape; - -public record Reservation(int id, String name, String date, String time) { -} diff --git a/src/main/java/roomescape/ReservationController.java b/src/main/java/roomescape/ReservationController.java deleted file mode 100644 index 6586ba510..000000000 --- a/src/main/java/roomescape/ReservationController.java +++ /dev/null @@ -1,33 +0,0 @@ -package roomescape; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -import java.util.ArrayList; -import java.util.List; - -@Controller -public class ReservationController { - - private final List reservations = new ArrayList<>(); - - public ReservationController() { - reservations.add(new Reservation(1, "브라운", "2023-01-01", "10:00")); - reservations.add(new Reservation(2, "브라운", "2023-01-02", "11:00")); - reservations.add(new Reservation(3, "브라운", "2023-01-03", "12:00")); - } - - @GetMapping("/reservation") - public String reservationPage() { - return "reservation"; - } - - @GetMapping("/reservations") - @ResponseBody - public List findAll() { - return reservations; - } -} - - diff --git a/src/main/java/roomescape/dao/ReservationDao.java b/src/main/java/roomescape/dao/ReservationDao.java new file mode 100644 index 000000000..7e74dd4e0 --- /dev/null +++ b/src/main/java/roomescape/dao/ReservationDao.java @@ -0,0 +1,62 @@ +package roomescape.dao; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.stereotype.Repository; +import roomescape.domain.Reservation; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Repository +public class ReservationDao { + + private final JdbcTemplate jdbcTemplate; + private final SimpleJdbcInsert simpleInsert; + + public ReservationDao(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.simpleInsert = new SimpleJdbcInsert(jdbcTemplate) + .withTableName("reservation") + .usingGeneratedKeyColumns("id"); + } + + private final RowMapper rowMapper = (resultSet, rowNum) -> + new Reservation( + resultSet.getLong("id"), + resultSet.getString("name"), + resultSet.getString("date"), + resultSet.getString("time") + ); + + public List findAll() { + return jdbcTemplate.query( + "SELECT id, name, date, time FROM reservation", + rowMapper + ); + } + + public long insert(String name, String date, String time) { + Map params = new HashMap<>(); + params.put("name", name); + params.put("date", date); + params.put("time", time); + + Number key = simpleInsert.executeAndReturnKey(params); + return key.longValue(); + } + + public Reservation findById(long id) { + return jdbcTemplate.queryForObject( + "SELECT id, name, date, time FROM reservation WHERE id = ?", + rowMapper, + id + ); + } + + public int deleteById(long id) { + return jdbcTemplate.update("DELETE FROM reservation WHERE id = ?", id); + } +} diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index dd2af4eb7..81e6290b1 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -1,19 +1,19 @@ package roomescape.domain; public class Reservation { - private final long id; + private final Long id; private final String name; private final String date; private final String time; - public Reservation(long id, String name, String date, String time) { + public Reservation(Long id, String name, String date, String time) { this.id = id; this.name = name; this.date = date; this.time = time; } - public long getId() { + public Long getId() { return id; } diff --git a/src/main/java/roomescape/exception/ReservationExceptionHandler.java b/src/main/java/roomescape/exception/ReservationExceptionHandler.java index e0440d7d1..40a4c990f 100644 --- a/src/main/java/roomescape/exception/ReservationExceptionHandler.java +++ b/src/main/java/roomescape/exception/ReservationExceptionHandler.java @@ -7,9 +7,16 @@ import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + @RestControllerAdvice public class ReservationExceptionHandler { + private static final Logger logger = Logger.getLogger("테스트용"); + @ExceptionHandler({ InvalidReservationRequestException.class, IllegalArgumentException.class, @@ -23,6 +30,10 @@ public ResponseEntity handleBadRequest(RuntimeException e) { @ExceptionHandler(NotFoundReservationException.class) public ResponseEntity handleNotFound(NotFoundReservationException e) { + String stackTrace = Arrays.stream(e.getStackTrace()) + .map(StackTraceElement::toString) + .collect(Collectors.joining("\n")); + logger.log(Level.INFO, stackTrace); return ResponseEntity.status(404).build(); } diff --git a/src/main/java/roomescape/service/ReservationList.java b/src/main/java/roomescape/service/ReservationList.java index bd83592f5..836ee5a32 100644 --- a/src/main/java/roomescape/service/ReservationList.java +++ b/src/main/java/roomescape/service/ReservationList.java @@ -1,6 +1,7 @@ package roomescape.service; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import roomescape.dao.ReservationDao; import roomescape.dto.ReservationResponse; import roomescape.dto.ReservationRequest; import roomescape.exception.InvalidReservationRequestException; @@ -9,22 +10,22 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.HashMap; -import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Logger; -@Component +@Service public class ReservationList { - private final Map idToReservation = new HashMap<>(); - private AtomicLong index; - public ReservationList() { - this.index = new AtomicLong(0); + private final ReservationDao reservationDao; + private static final Logger logger = Logger.getLogger(ReservationList.class.getName()); + + public ReservationList(ReservationDao reservationDao) { + this.reservationDao = reservationDao; } public List findAll() { + List results = reservationDao.findAll(); List responses = new ArrayList<>(); - for (Reservation reservation : idToReservation.values()) { + for (Reservation reservation : results) { responses.add(toResponse(reservation)); } return responses; @@ -32,31 +33,31 @@ public List findAll() { public ReservationResponse create(ReservationRequest request) { if (request.name() == null || request.name().isBlank() - || request.date() == null || request.date().isBlank() - || request.time() == null || request.time().isBlank()) { - throw new InvalidReservationRequestException( - "잘못된 예약 요청입니다. " + - "name=" + request.name() + - ", date=" + request.date() + - ", time=" + request.time() - ); + || request.date() == null || request.date().isBlank() + || request.time() == null || request.time().isBlank()) { + logger.warning("Invalid request: " + request); + throw new InvalidReservationRequestException("잘못된 예약 요청입니다."); } - long newId = index.incrementAndGet(); - Reservation reservation = new Reservation(newId, request.name(), request.date(), request.time()); - idToReservation.put(newId, reservation); - return toResponse(reservation); + long id = reservationDao.insert(request.name(), request.date(), request.time()); + Reservation saved = reservationDao.findById(id); + return toResponse(saved); } public void delete(long id) { - if (!idToReservation.containsKey(id)) { - throw new NotFoundReservationException("해당 분실물을 찾을 수 없습니다: " + id); + int updated = reservationDao.deleteById(id); + if (updated == 0) { + throw new NotFoundReservationException("해당 예약을 찾을 수 없습니다: " + id); } - idToReservation.remove(id); } private ReservationResponse toResponse(Reservation reservation) { - return new ReservationResponse(reservation.getId(), reservation.getName(), reservation.getDate(), reservation.getTime()); + return new ReservationResponse( + reservation.getId(), + reservation.getName(), + reservation.getDate(), + reservation.getTime() + ); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e69de29bb..822f3ed0e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -0,0 +1,5 @@ +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console + +spring.datasource.url=jdbc:h2:mem:database +spring.datasource.driver-class-name=org.h2.Driver \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 000000000..0a3d3a80e --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE reservation +( + id BIGINT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + date VARCHAR(255) NOT NULL, + time VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); + diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 110a734c5..18d7f1bfb 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -4,12 +4,30 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; + import static org.hamcrest.Matchers.is; import io.restassured.http.ContentType; import java.util.Map; import java.util.HashMap; +import io.restassured.http.ContentType; + +import java.util.Map; +import java.util.HashMap; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.sql.Connection; +import java.sql.SQLException; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import roomescape.domain.Reservation; + @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { @@ -36,7 +54,6 @@ public class MissionStepTest { .body("size()", is(0)); } - @Test void 삼단계() { Map params = new HashMap<>(); @@ -92,4 +109,65 @@ public class MissionStepTest { .then().log().all() .statusCode(404); } + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + void 오단계() { + try (Connection connection = jdbcTemplate.getDataSource().getConnection()) { + assertThat(connection).isNotNull(); + assertThat(connection.getCatalog()).isEqualTo("DATABASE"); + assertThat(connection.getMetaData().getTables(null, null, "RESERVATION", null).next()).isTrue(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Test + void 육단계() { + jdbcTemplate.update( + "INSERT INTO reservation (name, date, time) VALUES (?, ?, ?)", + "브라운", + "2023-08-05", + "15:40" + ); + + List reservations = RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200).extract() + .jsonPath().getList(".", Reservation.class); + + Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + + assertThat(reservations.size()).isEqualTo(count); + } + + @Test + void 칠단계() { + Map params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", "2023-08-05"); + params.put("time", "10:00"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations") + .then().log().all() + .statusCode(201) + .header("Location", "/reservations/1"); + + Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + assertThat(count).isEqualTo(1); + + RestAssured.given().log().all() + .when().delete("/reservations/1") + .then().log().all() + .statusCode(204); + + Integer countAfterDelete = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + assertThat(countAfterDelete).isEqualTo(0); + } }