From 17cd0d98864d71adcf93d43d910b800001812f6b Mon Sep 17 00:00:00 2001 From: kimihwa Date: Thu, 27 Jun 2024 11:53:41 +0900 Subject: [PATCH 1/7] =?UTF-8?q?1=EB=8B=A8=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ .../roomescape/controller/WelcomeController.java | 12 ++++++++++++ .../resources/templates/{home.html => index.html} | 0 3 files changed, 14 insertions(+) create mode 100644 src/main/java/roomescape/controller/WelcomeController.java rename src/main/resources/templates/{home.html => index.html} (100%) diff --git a/build.gradle b/build.gradle index 57267157c..40a581b15 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,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-thymeleaf' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' } diff --git a/src/main/java/roomescape/controller/WelcomeController.java b/src/main/java/roomescape/controller/WelcomeController.java new file mode 100644 index 000000000..b03021846 --- /dev/null +++ b/src/main/java/roomescape/controller/WelcomeController.java @@ -0,0 +1,12 @@ +package roomescape.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class WelcomeController { + @GetMapping("/") + public String welcome() { + return "index"; + } +} diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/index.html similarity index 100% rename from src/main/resources/templates/home.html rename to src/main/resources/templates/index.html From 13cd4b3cf5c62d60365188f7d4c76ee1309a34a3 Mon Sep 17 00:00:00 2001 From: kimihwa Date: Thu, 27 Jun 2024 11:59:07 +0900 Subject: [PATCH 2/7] =?UTF-8?q?2=EB=8B=A8=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 30 ++++++++++++++++++ .../java/roomescape/model/Reservation.java | 31 +++++++++++++++++++ src/main/resources/templates/reservation.html | 9 ++++++ src/test/java/roomescape/MissionStepTest.java | 16 ++++++++++ 4 files changed, 86 insertions(+) create mode 100644 src/main/java/roomescape/controller/ReservationController.java create mode 100644 src/main/java/roomescape/model/Reservation.java diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java new file mode 100644 index 000000000..21c7d7b14 --- /dev/null +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -0,0 +1,30 @@ +package roomescape.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import roomescape.model.Reservation; + +import java.util.ArrayList; +import java.util.List; + +@Controller +public class ReservationController { + + private List reservations = new ArrayList<>(); + + @GetMapping("/reservation") + public String reservation() { + return "reservation"; + } + + @GetMapping("/reservations") + @ResponseBody + public List getReservations() { + reservations.add(new Reservation(1L, "브라운", "2023-01-01", "10:00")); + reservations.add(new Reservation(2L, "브라운", "2023-01-02", "11:00")); + reservations.add(new Reservation(3L, "브라운", "2023-01-03", "12:00")); + + return reservations; + } +} diff --git a/src/main/java/roomescape/model/Reservation.java b/src/main/java/roomescape/model/Reservation.java new file mode 100644 index 000000000..64eeb4227 --- /dev/null +++ b/src/main/java/roomescape/model/Reservation.java @@ -0,0 +1,31 @@ +package roomescape.model; + +public class Reservation { + private Long id; + private String name; + private String date; + private 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() { + return id; + } + + public String getName() { + return name; + } + + public String getDate() { + return date; + } + + public String getTime() { + return time; + } +} diff --git a/src/main/resources/templates/reservation.html b/src/main/resources/templates/reservation.html index 7c0eb9fc4..525f6d415 100644 --- a/src/main/resources/templates/reservation.html +++ b/src/main/resources/templates/reservation.html @@ -56,6 +56,15 @@

예약 관리

+ + + + + + + + + diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index cf4efbe91..946f3ad05 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -5,6 +5,8 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import static org.hamcrest.Matchers.is; + @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { @@ -16,4 +18,18 @@ public class MissionStepTest { .then().log().all() .statusCode(200); } + + @Test + void 이단계() { + RestAssured.given().log().all() + .when().get("/reservation") + .then().log().all() + .statusCode(200); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("size()", is(3)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. + } } From 63d8d62247d097d43f2b74759dca3632592b2e6e Mon Sep 17 00:00:00 2001 From: kimihwa Date: Thu, 27 Jun 2024 12:03:11 +0900 Subject: [PATCH 3/7] =?UTF-8?q?3=EB=8B=A8=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 31 ++++++++++++--- src/test/java/roomescape/MissionStepTest.java | 38 +++++++++++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 21c7d7b14..2ec5815d2 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -1,18 +1,22 @@ package roomescape.controller; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.*; import roomescape.model.Reservation; +import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; @Controller public class ReservationController { private List reservations = new ArrayList<>(); + private AtomicLong index = new AtomicLong(1); + @GetMapping("/reservation") public String reservation() { return "reservation"; @@ -21,10 +25,25 @@ public String reservation() { @GetMapping("/reservations") @ResponseBody public List getReservations() { - reservations.add(new Reservation(1L, "브라운", "2023-01-01", "10:00")); - reservations.add(new Reservation(2L, "브라운", "2023-01-02", "11:00")); - reservations.add(new Reservation(3L, "브라운", "2023-01-03", "12:00")); - return reservations; } + + @PostMapping("/reservations") + public ResponseEntity createReservation(@RequestBody Reservation reservation) { + Long id = (long) (reservations.size() + 1); + String name = reservation.getName(); + String date = reservation.getDate(); + String time = reservation.getTime(); + + Reservation newReservation = new Reservation(id, name, date, time); + reservations.add(newReservation); + + return ResponseEntity.created(URI.create("/reservations/" + newReservation.getId())).body(newReservation); + } + + @DeleteMapping("/reservations/{id}") + public ResponseEntity deleteReservation(@PathVariable Long id) { + reservations.removeIf(r -> r.getId() == id); + return ResponseEntity.noContent().build(); + } } diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 946f3ad05..39144dd69 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -1,10 +1,14 @@ package roomescape; import io.restassured.RestAssured; +import io.restassured.http.ContentType; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import java.util.HashMap; +import java.util.Map; + import static org.hamcrest.Matchers.is; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @@ -32,4 +36,38 @@ public class MissionStepTest { .statusCode(200) .body("size()", is(3)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. } + + @Test + void 삼단계() { + Map params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", "2023-08-05"); + params.put("time", "15:40"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations") + .then().log().all() + .statusCode(201) + .header("Location", "/reservations/1") + .body("id", is(1)); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("size()", is(1)); + + RestAssured.given().log().all() + .when().delete("/reservations/1") + .then().log().all() + .statusCode(204); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("size()", is(0)); + } } From a9d5050ea3d61d4f64326f3e3b7a40ad12f2b2ac Mon Sep 17 00:00:00 2001 From: kimihwa Date: Thu, 27 Jun 2024 12:06:37 +0900 Subject: [PATCH 4/7] =?UTF-8?q?4=EB=8B=A8=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 26 ++++++++++++++++++- .../InvalidReservationException.java | 7 +++++ .../NotFoundReservationException.java | 7 +++++ src/test/java/roomescape/MissionStepTest.java | 22 ++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/main/java/roomescape/exception/InvalidReservationException.java create mode 100644 src/main/java/roomescape/exception/NotFoundReservationException.java diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 2ec5815d2..69cebe7a5 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -1,8 +1,12 @@ package roomescape.controller; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.WebRequest; +import roomescape.exception.InvalidReservationException; +import roomescape.exception.NotFoundReservationException; import roomescape.model.Reservation; import java.net.URI; @@ -29,7 +33,10 @@ public List getReservations() { } @PostMapping("/reservations") + @ResponseBody public ResponseEntity createReservation(@RequestBody Reservation reservation) { + validateReservation(reservation); + Long id = (long) (reservations.size() + 1); String name = reservation.getName(); String date = reservation.getDate(); @@ -42,8 +49,25 @@ public ResponseEntity createReservation(@RequestBody Reservation re } @DeleteMapping("/reservations/{id}") + @ResponseBody public ResponseEntity deleteReservation(@PathVariable Long id) { - reservations.removeIf(r -> r.getId() == id); + boolean removed = reservations.removeIf(r -> r.getId() == id); + if (!removed) { + throw new NotFoundReservationException("Reservation not found with id: " + id); + } return ResponseEntity.noContent().build(); } + + @ExceptionHandler({InvalidReservationException.class, NotFoundReservationException.class}) + public ResponseEntity handleException(RuntimeException e, WebRequest request) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + + private void validateReservation(Reservation reservation) { + if (reservation.getName() == null || reservation.getName().isEmpty() || + reservation.getDate() == null || reservation.getDate().isEmpty() || + reservation.getTime() == null || reservation.getTime().isEmpty()) { + throw new InvalidReservationException("예약 생성 시 필요한 인자가 비어 있습니다"); + } + } } diff --git a/src/main/java/roomescape/exception/InvalidReservationException.java b/src/main/java/roomescape/exception/InvalidReservationException.java new file mode 100644 index 000000000..3af57ac28 --- /dev/null +++ b/src/main/java/roomescape/exception/InvalidReservationException.java @@ -0,0 +1,7 @@ +package roomescape.exception; + +public class InvalidReservationException extends RuntimeException{ + public InvalidReservationException(String message) { + super(message); + } +} diff --git a/src/main/java/roomescape/exception/NotFoundReservationException.java b/src/main/java/roomescape/exception/NotFoundReservationException.java new file mode 100644 index 000000000..0a214534c --- /dev/null +++ b/src/main/java/roomescape/exception/NotFoundReservationException.java @@ -0,0 +1,7 @@ +package roomescape.exception; + +public class NotFoundReservationException extends RuntimeException{ + public NotFoundReservationException(String message) { + super(message); + } +} diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 39144dd69..c8615ff55 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -70,4 +70,26 @@ public class MissionStepTest { .statusCode(200) .body("size()", is(0)); } + + @Test + void 사단계() { + Map params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", ""); + params.put("time", ""); + + // 필요한 인자가 없는 경우 + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations") + .then().log().all() + .statusCode(400); + + // 삭제할 예약이 없는 경우 + RestAssured.given().log().all() + .when().delete("/reservations/1") + .then().log().all() + .statusCode(400); + } } From 7a8a7443ed40c92553702003ef62f0eb48a5e7bf Mon Sep 17 00:00:00 2001 From: kimihwa Date: Thu, 4 Jul 2024 17:00:39 +0900 Subject: [PATCH 5/7] =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 0 -> 6148 bytes .../controller/ReservationController.java | 18 +++++++++++------- src/test/java/roomescape/MissionStepTest.java | 8 ++++++-- 3 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c0878a625252c79c09d154bd31ed61769339782a GIT binary patch literal 6148 zcmeHLy-EW?5S}%cAJigP1QCU^vJ>+LXNVSBSmXgT$%Q0x;er~l5V zh>e|%olhW&jrao2?5@df?qVsTGqC&J&Ckx{yWCDT03b>|zX*^AfE*4&XA-9bAv=wj zOlZw4qM$W`X0cKaJKdNkE0_Uh;5RZr@2&&O(1bP|Snsd6yEbi|uw1SMVGVV;!OXqWv3nSjFA=$&q4_7LG4SHBX0KS!fzVIyb53FYX z*X{Q&9@1cV3^_>lu<|c>ls|*tg5;+GAM2xbvxbY!+Zp{VtTjv)#`#!v2HqgW`G%B1_lh!{@~&u^fX2a<<^0d zJOUs(sFs4)at}vzNrC8Tj1-~Rss$!c)jIGr$bk45ZX;p8EgV`18LV#COa9Gw`Pv5Kg&LF5ySgy*2jXsMoqUc5sl8 pU!+im;H2BJyr5Q0|0_sAn@bmnp2kQaYEbxxfTV#ZX5dE|_ymx=yodk* literal 0 HcmV?d00001 diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 69cebe7a5..6badd1a18 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -37,7 +37,7 @@ public List getReservations() { public ResponseEntity createReservation(@RequestBody Reservation reservation) { validateReservation(reservation); - Long id = (long) (reservations.size() + 1); + Long id = index.getAndIncrement(); String name = reservation.getName(); String date = reservation.getDate(); String time = reservation.getTime(); @@ -53,7 +53,7 @@ public ResponseEntity createReservation(@RequestBody Reservation re public ResponseEntity deleteReservation(@PathVariable Long id) { boolean removed = reservations.removeIf(r -> r.getId() == id); if (!removed) { - throw new NotFoundReservationException("Reservation not found with id: " + id); + throw new NotFoundReservationException("예약을 찾을 수 없습니다: " + id); } return ResponseEntity.noContent().build(); } @@ -64,10 +64,14 @@ public ResponseEntity handleException(RuntimeException e, WebRequest req } private void validateReservation(Reservation reservation) { - if (reservation.getName() == null || reservation.getName().isEmpty() || - reservation.getDate() == null || reservation.getDate().isEmpty() || - reservation.getTime() == null || reservation.getTime().isEmpty()) { - throw new InvalidReservationException("예약 생성 시 필요한 인자가 비어 있습니다"); - } + if (reservation.getName() == null || reservation.getName().isEmpty()) { + throw new InvalidReservationException("이름이 필요합니다."); + } + if (reservation.getDate() == null || reservation.getDate().isEmpty()) { + throw new InvalidReservationException("날짜가 필요합니다."); + } + if (reservation.getTime() == null || reservation.getTime().isEmpty()) { + throw new InvalidReservationException("시간이 필요합니다"); + } } } diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index c8615ff55..173deadff 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -2,6 +2,7 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; @@ -15,6 +16,7 @@ @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { + @DisplayName("루트 경로 확인") @Test void 일단계() { RestAssured.given().log().all() @@ -23,6 +25,7 @@ public class MissionStepTest { .statusCode(200); } + @DisplayName("예약 조회 확인") @Test void 이단계() { RestAssured.given().log().all() @@ -34,9 +37,10 @@ public class MissionStepTest { .when().get("/reservations") .then().log().all() .statusCode(200) - .body("size()", is(3)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. + .body("size()", is(0)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. } + @DisplayName("예약 생성, 조회, 삭제 확인") @Test void 삼단계() { Map params = new HashMap<>(); @@ -70,7 +74,7 @@ public class MissionStepTest { .statusCode(200) .body("size()", is(0)); } - + @DisplayName("예외 처리 확인") @Test void 사단계() { Map params = new HashMap<>(); From fdfdee3bbde6be5c34f67eacdba64ddad4df1285 Mon Sep 17 00:00:00 2001 From: kimihwa Date: Mon, 8 Jul 2024 00:23:59 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20?= =?UTF-8?q?=EB=B0=8F=20spring=20jdbc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + .../controller/ReservationController.java | 62 +++++++--------- .../exception/GlobalExceptionHandler.java | 29 ++++++++ .../InvalidReservationException.java | 9 ++- .../NotFoundReservationException.java | 10 ++- .../java/roomescape/model/Reservation.java | 1 + .../respository/ReservationRepository.java | 57 ++++++++++++++ src/main/resources/application.properties | 5 ++ src/main/resources/schema.sql | 8 ++ src/test/java/roomescape/MissionStepTest.java | 74 +++++++++++++++++-- 10 files changed, 213 insertions(+), 45 deletions(-) create mode 100644 src/main/java/roomescape/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/roomescape/respository/ReservationRepository.java create mode 100644 src/main/resources/schema.sql diff --git a/build.gradle b/build.gradle index 40a581b15..efbf27167 100644 --- a/build.gradle +++ b/build.gradle @@ -16,8 +16,11 @@ 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' + runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' + testImplementation 'org.assertj:assertj-core:3.20.2' } test { diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 6badd1a18..593d8be78 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -1,77 +1,67 @@ package roomescape.controller; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; -import org.springframework.web.context.request.WebRequest; import roomescape.exception.InvalidReservationException; -import roomescape.exception.NotFoundReservationException; import roomescape.model.Reservation; +import roomescape.respository.ReservationRepository; import java.net.URI; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; -@Controller +@RestController +@RequestMapping("/reservations") public class ReservationController { - - private List reservations = new ArrayList<>(); - private AtomicLong index = new AtomicLong(1); + private final ReservationRepository reservationRepository; - @GetMapping("/reservation") - public String reservation() { - return "reservation"; + public ReservationController(ReservationRepository reservationRepository) { + this.reservationRepository = reservationRepository; } - @GetMapping("/reservations") + @GetMapping @ResponseBody public List getReservations() { - return reservations; + return reservationRepository.findAllReservations(); + } + + @GetMapping("/{id}") + @ResponseBody + public Reservation findReservationById(@PathVariable Long id) { + return reservationRepository.findReservationById(id); } - @PostMapping("/reservations") + @PostMapping @ResponseBody public ResponseEntity createReservation(@RequestBody Reservation reservation) { validateReservation(reservation); Long id = index.getAndIncrement(); - String name = reservation.getName(); - String date = reservation.getDate(); - String time = reservation.getTime(); - Reservation newReservation = new Reservation(id, name, date, time); - reservations.add(newReservation); + Reservation newReservation = new Reservation(id, reservation.getName(), reservation.getDate(), reservation.getTime()); + reservationRepository.insert(newReservation); return ResponseEntity.created(URI.create("/reservations/" + newReservation.getId())).body(newReservation); } - @DeleteMapping("/reservations/{id}") + @DeleteMapping("/{id}") @ResponseBody public ResponseEntity deleteReservation(@PathVariable Long id) { - boolean removed = reservations.removeIf(r -> r.getId() == id); - if (!removed) { - throw new NotFoundReservationException("예약을 찾을 수 없습니다: " + id); - } + reservationRepository.delete(id); return ResponseEntity.noContent().build(); } - @ExceptionHandler({InvalidReservationException.class, NotFoundReservationException.class}) - public ResponseEntity handleException(RuntimeException e, WebRequest request) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); - } - private void validateReservation(Reservation reservation) { if (reservation.getName() == null || reservation.getName().isEmpty()) { - throw new InvalidReservationException("이름이 필요합니다."); - } + throw new InvalidReservationException("name", "이름이 필요합니다."); + } if (reservation.getDate() == null || reservation.getDate().isEmpty()) { - throw new InvalidReservationException("날짜가 필요합니다."); - } + throw new InvalidReservationException("date", "날짜가 필요합니다."); + } if (reservation.getTime() == null || reservation.getTime().isEmpty()) { - throw new InvalidReservationException("시간이 필요합니다"); - } + throw new InvalidReservationException("time", "시간이 필요합니다."); + } } + } diff --git a/src/main/java/roomescape/exception/GlobalExceptionHandler.java b/src/main/java/roomescape/exception/GlobalExceptionHandler.java new file mode 100644 index 000000000..562dd5d41 --- /dev/null +++ b/src/main/java/roomescape/exception/GlobalExceptionHandler.java @@ -0,0 +1,29 @@ +package roomescape.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(NotFoundReservationException.class) + public ResponseEntity handleNotFoundReservationException(NotFoundReservationException e, WebRequest request) { + String message = "예약을 찾을 수 없습니다: " + e.getReservationId(); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body((e.getMessage())); + } + + @ExceptionHandler(InvalidReservationException.class) + public ResponseEntity handleInvalidReservationException(InvalidReservationException e, WebRequest request) { + String message = e.getFieldName() + "'의 값이 유용하지 않습니다: " + e.getMessage(); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGlobalException(Exception e, WebRequest request) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("예기치 않은 오류가 발생했습니다: " + e.getMessage()); + } + +} diff --git a/src/main/java/roomescape/exception/InvalidReservationException.java b/src/main/java/roomescape/exception/InvalidReservationException.java index 3af57ac28..00587601b 100644 --- a/src/main/java/roomescape/exception/InvalidReservationException.java +++ b/src/main/java/roomescape/exception/InvalidReservationException.java @@ -1,7 +1,14 @@ package roomescape.exception; public class InvalidReservationException extends RuntimeException{ - public InvalidReservationException(String message) { + private String fieldName; + + public InvalidReservationException(String fieldName, String message) { super(message); + this.fieldName = fieldName; + } + + public String getFieldName() { + return fieldName; } } diff --git a/src/main/java/roomescape/exception/NotFoundReservationException.java b/src/main/java/roomescape/exception/NotFoundReservationException.java index 0a214534c..fa86296ad 100644 --- a/src/main/java/roomescape/exception/NotFoundReservationException.java +++ b/src/main/java/roomescape/exception/NotFoundReservationException.java @@ -1,7 +1,13 @@ package roomescape.exception; public class NotFoundReservationException extends RuntimeException{ - public NotFoundReservationException(String message) { - super(message); + private Long reservationId; + public NotFoundReservationException(Long reservationId) { + super("예약을 찾을 수 없습니다: " + reservationId); + this.reservationId = reservationId; + } + + public Long getReservationId() { + return reservationId; } } diff --git a/src/main/java/roomescape/model/Reservation.java b/src/main/java/roomescape/model/Reservation.java index 64eeb4227..32417b784 100644 --- a/src/main/java/roomescape/model/Reservation.java +++ b/src/main/java/roomescape/model/Reservation.java @@ -28,4 +28,5 @@ public String getDate() { public String getTime() { return time; } + } diff --git a/src/main/java/roomescape/respository/ReservationRepository.java b/src/main/java/roomescape/respository/ReservationRepository.java new file mode 100644 index 000000000..6929ec448 --- /dev/null +++ b/src/main/java/roomescape/respository/ReservationRepository.java @@ -0,0 +1,57 @@ +package roomescape.respository; + +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import roomescape.exception.NotFoundReservationException; +import roomescape.model.Reservation; + +import java.util.List; + +@Repository +public class ReservationRepository { + + private JdbcTemplate jdbcTemplate; + + public ReservationRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + private final RowMapper rowMapper = (resultSet, rowNum) -> { + Reservation reservation = new Reservation( + resultSet.getLong("id"), + resultSet.getString("name"), + resultSet.getString("date"), + resultSet.getString("time")); + return reservation; + }; + + public List findAllReservations() { + String sql = "SELECT id, name, date, time FROM reservation"; + return jdbcTemplate.query(sql, rowMapper); + } + + public Reservation findReservationById(Long id) { + String sql = "SELECT id, name, date, time FROM reservation where id = ?"; + try { + return jdbcTemplate.queryForObject(sql, rowMapper, id); + } catch (EmptyResultDataAccessException e) { + throw new NotFoundReservationException(id); + } + } + + public void insert(Reservation reservation) { + String sql = "INSERT INTO reservation(name, date, time) VALUES (?, ?, ?)"; + jdbcTemplate.update(sql, reservation.getName(), reservation.getDate(), reservation.getTime()); + } + + public int delete(Long id) { + String sql = "DELETE FROM reservation WHERE id = ?"; + int row = jdbcTemplate.update(sql, id); + if (row == 0) { + throw new NotFoundReservationException(id); + } + return row; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e69de29bb..dc4a6e8c3 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 + + diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 000000000..8d9ab2754 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,8 @@ +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 173deadff..9bfe9c346 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -4,18 +4,28 @@ import io.restassured.http.ContentType; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.annotation.DirtiesContext; +import roomescape.model.Reservation; +import java.sql.Connection; +import java.sql.SQLException; import java.util.HashMap; +import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { + @Autowired + private JdbcTemplate jdbcTemplate; + @DisplayName("루트 경로 확인") @Test void 일단계() { @@ -28,11 +38,6 @@ public class MissionStepTest { @DisplayName("예약 조회 확인") @Test void 이단계() { - RestAssured.given().log().all() - .when().get("/reservation") - .then().log().all() - .statusCode(200); - RestAssured.given().log().all() .when().get("/reservations") .then().log().all() @@ -94,6 +99,63 @@ public class MissionStepTest { RestAssured.given().log().all() .when().delete("/reservations/1") .then().log().all() - .statusCode(400); + .statusCode(404); + } + + @DisplayName("데이터 베이스 연결 및 테이블 확인") + @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); + } + } + + @DisplayName("데이터 조회 확인") + @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); + } + + @DisplayName("데이터 추가 및 삭제 확인") + @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); } + } From 04f3a9b71c10d58a48ea3db11a809de2ded483ca Mon Sep 17 00:00:00 2001 From: kimihwa Date: Fri, 12 Jul 2024 10:56:29 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 7 +------ .../respository/ReservationRepository.java | 21 ++++++++++++++++--- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 593d8be78..380c485e9 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -8,12 +8,10 @@ import java.net.URI; import java.util.List; -import java.util.concurrent.atomic.AtomicLong; @RestController @RequestMapping("/reservations") public class ReservationController { - private AtomicLong index = new AtomicLong(1); private final ReservationRepository reservationRepository; public ReservationController(ReservationRepository reservationRepository) { @@ -37,10 +35,7 @@ public Reservation findReservationById(@PathVariable Long id) { public ResponseEntity createReservation(@RequestBody Reservation reservation) { validateReservation(reservation); - Long id = index.getAndIncrement(); - - Reservation newReservation = new Reservation(id, reservation.getName(), reservation.getDate(), reservation.getTime()); - reservationRepository.insert(newReservation); + Reservation newReservation = reservationRepository.insert(reservation); return ResponseEntity.created(URI.create("/reservations/" + newReservation.getId())).body(newReservation); } diff --git a/src/main/java/roomescape/respository/ReservationRepository.java b/src/main/java/roomescape/respository/ReservationRepository.java index 6929ec448..882c39ac9 100644 --- a/src/main/java/roomescape/respository/ReservationRepository.java +++ b/src/main/java/roomescape/respository/ReservationRepository.java @@ -3,10 +3,13 @@ import org.springframework.dao.EmptyResultDataAccessException; 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.exception.NotFoundReservationException; import roomescape.model.Reservation; +import java.sql.PreparedStatement; import java.util.List; @Repository @@ -41,9 +44,21 @@ public Reservation findReservationById(Long id) { } } - public void insert(Reservation reservation) { - String sql = "INSERT INTO reservation(name, date, time) VALUES (?, ?, ?)"; - jdbcTemplate.update(sql, reservation.getName(), reservation.getDate(), reservation.getTime()); + public Reservation insert(Reservation reservation) { + + KeyHolder keyHolder = new GeneratedKeyHolder(); + + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement( + "INSERT INTO reservation(name, date, time) VALUES (?, ?, ?)", new String[]{"id"}); + ps.setString(1, reservation.getName()); + ps.setString(2, reservation.getDate()); + ps.setString(3, reservation.getTime()); + return ps; + }, keyHolder); + + Long id = keyHolder.getKey().longValue(); + return new Reservation(id, reservation.getName(), reservation.getDate(), reservation.getTime()); } public int delete(Long id) {