Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Spring JDBC] 김이화 미션 제출합니다. #295

Merged
merged 8 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
Binary file added .DS_Store
Binary file not shown.
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ 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'
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 {
Expand Down
67 changes: 67 additions & 0 deletions src/main/java/roomescape/controller/ReservationController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package roomescape.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import roomescape.exception.InvalidReservationException;
import roomescape.model.Reservation;
import roomescape.respository.ReservationRepository;

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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

미션 요구 조건에 제거하라고 나와 있어서 제거하고 구현하는 게 좋을 것 같아요!

(혹시나 제가 요구 조건을 잘못 이해한 거라면 말씀해주세용~)

image

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉 지워서 제출하겠습니다

private final ReservationRepository reservationRepository;

public ReservationController(ReservationRepository reservationRepository) {
this.reservationRepository = reservationRepository;
}

@GetMapping
@ResponseBody
public List<Reservation> getReservations() {
return reservationRepository.findAllReservations();
}

@GetMapping("/{id}")
@ResponseBody
public Reservation findReservationById(@PathVariable Long id) {
return reservationRepository.findReservationById(id);
}
Comment on lines +27 to +31

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기에서는 왜 따로 ResponseEntity 활용을 안했는지 궁금합니당

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResponseEntity를 사용하지 않아도 예외가 발생하면 GlobalExceptionHandler에서 처리해줘서 크게 문제가 안 된다고 생각했습니다


@PostMapping
@ResponseBody
public ResponseEntity<Reservation> createReservation(@RequestBody Reservation reservation) {
validateReservation(reservation);

Long id = index.getAndIncrement();

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("/{id}")
@ResponseBody
public ResponseEntity<Void> deleteReservation(@PathVariable Long id) {
reservationRepository.delete(id);
return ResponseEntity.noContent().build();
}

private void validateReservation(Reservation reservation) {
if (reservation.getName() == null || reservation.getName().isEmpty()) {
throw new InvalidReservationException("name", "이름이 필요합니다.");
}
if (reservation.getDate() == null || reservation.getDate().isEmpty()) {
throw new InvalidReservationException("date", "날짜가 필요합니다.");
}
if (reservation.getTime() == null || reservation.getTime().isEmpty()) {
throw new InvalidReservationException("time", "시간이 필요합니다.");
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateReservation 메소드 분리 할 수 있지 않을까요?! 중복된 코드를 줄이면 가독성이 좋아질 것 같아요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[spring core]에 리팩토링 해서 넣었습니다. 클래스로 나눠서 예외 처리 패키지에 넣었는데, 이걸 말씀하시는 게 맞을까요?


}
12 changes: 12 additions & 0 deletions src/main/java/roomescape/controller/WelcomeController.java
Original file line number Diff line number Diff line change
@@ -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";
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

홈페이지 컨트롤러와 CR(U)D 컨트롤러를 따로 나눈 거 좋아보여요!

29 changes: 29 additions & 0 deletions src/main/java/roomescape/exception/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -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<String> handleNotFoundReservationException(NotFoundReservationException e, WebRequest request) {
String message = "예약을 찾을 수 없습니다: " + e.getReservationId();
return ResponseEntity.status(HttpStatus.NOT_FOUND).body((e.getMessage()));
}

@ExceptionHandler(InvalidReservationException.class)
public ResponseEntity<String> 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<String> handleGlobalException(Exception e, WebRequest request) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("예기치 않은 오류가 발생했습니다: " + e.getMessage());
}

}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

커스텀 예외처리 멋있어용👍👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package roomescape.exception;

public class InvalidReservationException extends RuntimeException{
private String fieldName;

public InvalidReservationException(String fieldName, String message) {
super(message);
this.fieldName = fieldName;
}

public String getFieldName() {
return fieldName;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception 명확하게 구분한 것 좋은 것 같아요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package roomescape.exception;

public class NotFoundReservationException extends RuntimeException{
private Long reservationId;
public NotFoundReservationException(Long reservationId) {
super("예약을 찾을 수 없습니다: " + reservationId);
this.reservationId = reservationId;
}

public Long getReservationId() {
return reservationId;
}
}
32 changes: 32 additions & 0 deletions src/main/java/roomescape/model/Reservation.java

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RequestDto와 ResponseDto를 만들어 엔티티 분리하면 더 좋을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[spring core]에 리팩토링 해서 만들었습니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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;
}

}
57 changes: 57 additions & 0 deletions src/main/java/roomescape/respository/ReservationRepository.java
Original file line number Diff line number Diff line change
@@ -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<Reservation> rowMapper = (resultSet, rowNum) -> {
Reservation reservation = new Reservation(
resultSet.getLong("id"),
resultSet.getString("name"),
resultSet.getString("date"),
resultSet.getString("time"));
return reservation;
};

public List<Reservation> 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);
}
}
Comment on lines +38 to +45

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 원래 DAO에 대해서 잘 알고 있었나요? 능숙해보여요

EmptyResultDataAccessException가 무슨 예외인지 궁금합니당

Copy link
Author

@ihwag719 ihwag719 Jul 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아뇨 초록 스터디 학습테스트에서 열심히 해봤는데도 사용하는데 어려움이 많았습니다 ㅠㅠ
EmptyResultDataAccessException는 JDBC를 사용할 때 데이터베이스에서 조회 결과가 없을 경우 발생하는 예외라고 해서 사용했습니다!


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;
}
}
5 changes: 5 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.url=jdbc:h2:mem:database


8 changes: 8 additions & 0 deletions src/main/resources/schema.sql
Original file line number Diff line number Diff line change
@@ -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)
);
9 changes: 9 additions & 0 deletions src/main/resources/templates/reservation.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ <h1>예약 관리</h1>
</tr>
</thead>
<tbody id="reservation-table-body">
<tr th:each="reservation, stat : ${reservations}">
<td th:text="${stat.count + 1}"></td>
<td th:text="${reservation.name}"></td>
<td th:text="${reservation.date}"></td>
<td th:text="${reservation.time}"></td>
<td>
<button class="btn btn-danger">삭제</button>
</td>
</tr>
</tbody>
</table>
</div>
Expand Down
Loading