-
Notifications
You must be signed in to change notification settings - Fork 132
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] 황승준 미션 제출합니다. #378
base: davidolleh
Are you sure you want to change the base?
Changes from all commits
766de30
8f583de
ed41d53
1d148ec
58d780c
4ddedd9
a555095
ceeec41
c023e3c
d9ad36b
e395263
faa20cf
f795d78
df0f34f
14c2227
7dfd9e4
2e56edb
d7e0290
8f48143
c6d091f
75bff4a
697005d
77a24b6
2aaa48f
fa1537c
989cad9
4663949
907e629
b148d0a
1274a15
4257069
3339d0a
3bab9cb
ed6dc7a
66087ad
005b552
78945f2
0d68891
fff2d60
beba048
4aae3f0
33f4f2a
68648b3
e9a35c2
8530260
31ba8b8
4ef725d
20480eb
e6d5f11
f430a17
f3a8644
36be467
313bf0d
305af35
351d38a
0147fd8
05284cc
5080574
a3fcedb
d02b685
22bd9ca
ee65dce
579edb6
42f2ea8
e03aad0
ecd7119
4d775b5
86d53b8
39859b2
1467f7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,3 +35,5 @@ out/ | |
|
||
### VS Code ### | ||
.vscode/ | ||
|
||
/log |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# 간단 스프링 어플리케이션 | ||
|
||
## JDBC | ||
### 5단계 | ||
- [x] 데이터베이스 설정 | ||
- [x] 데이터베이스 연결 | ||
|
||
### 6단계 | ||
- [x] 데이터 조회하기 | ||
|
||
### 7단계 | ||
- [x] 데이터 추가하기 | ||
- [x] 데이터 삭제하기 | ||
- [x] 데이터 삭제 잘못된 요청시 예외처리 | ||
|
||
### 7 단계 고민 | ||
- Entity id에 setter 함수를 두면 위험성이 크다는 생각이 들어 새로운 객체를 생성해서 return 해야 되겠다 라는 생각을 가지게 되었습니다 | ||
과연 spring은 새로 db에 생성된 entity에 관해 어떻게 id를 주입하나 궁금함군요 🤔 | ||
|
||
### 질문사항 | ||
- 어플리케이션 실행중 어디서든 exception이 발생하면 ControllerAdvice를 exception과 관련된 응답을 전달해줍니다. | ||
대부분의 exception을 최종적으로 ControllerAdvice에서 처리를 하다 보니 Service, Repository, Entity 등등 아무데서나 exception을 | ||
던질 수 있겠다라는 착각을 하게 되는것 같습니다. 혹시 exception은 보통 어느 layer에서 처리를 하는지 궁금하며 예외처리는 어떻게 관리 | ||
되는지 궁금합니다! | ||
Comment on lines
+20
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분은 현실적으로 말하면 프로젝트 바이 프로젝트가 너무 심하고, 팀 바이 팀이 너무 심해요 다르게 말하자면 throw 를 했다 -> 잘못된 응답을 줄 필요가 없다 (자체적으로 롤백을 할 수 있다) |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package roomescape.api; | ||
|
||
import jakarta.validation.Valid; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
import roomescape.api.dto.ReservationRequestDto; | ||
import roomescape.api.dto.ReservationResponseDto; | ||
import roomescape.entity.Reservation; | ||
import roomescape.service.ReservationService; | ||
|
||
import java.util.List; | ||
|
||
@RestController | ||
public class ReservationController { | ||
|
||
private final ReservationService reservationService; | ||
|
||
public ReservationController(ReservationService reservationService) { | ||
this.reservationService = reservationService; | ||
} | ||
|
||
@GetMapping("/reservations") | ||
public ResponseEntity<List<ReservationResponseDto>> readReservations() { | ||
return ResponseEntity | ||
.ok() | ||
.body( | ||
reservationService.readReservations().stream(). | ||
map(ReservationResponseDto::fromEntity) | ||
.toList() | ||
Comment on lines
+28
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. controller 레이어에서 dto 를 만드는 것과, service 레이어에서 dto 를 만드는 것중 전자를 선택한 이유가 궁금합니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 개인적인 생각으로 dto로 형변환하는 것은 핵심 비지니스 로직으로 생각하지 않는거 같습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 그런 근거가 있다면 좋은 것 같아요! 회사 기준을 봤을 때는 그냥 그때그때 바꾼다가 가장 일반적인 케이스긴 하거든요? |
||
); | ||
} | ||
|
||
@GetMapping("/reservations/{id}") | ||
public ResponseEntity<ReservationResponseDto> readReservations(@PathVariable Long id) { | ||
Reservation reservation = reservationService.readReservation(id); | ||
|
||
return ResponseEntity | ||
.ok() | ||
.body(ReservationResponseDto.fromEntity(reservation)); | ||
} | ||
|
||
@PostMapping("/reservations") | ||
public ResponseEntity<ReservationResponseDto> createReservation( | ||
@RequestBody @Valid ReservationRequestDto reservationDto | ||
) { | ||
ReservationResponseDto response = | ||
ReservationResponseDto.fromEntity(reservationService.createReservation(reservationDto.toEntity())); | ||
|
||
return ResponseEntity | ||
.status(HttpStatus.CREATED) | ||
.header("Location", "/reservations/"+ response.id()) | ||
.body(response); | ||
} | ||
|
||
@DeleteMapping("/reservations/{id}") | ||
public ResponseEntity<Void> deleteReservation(@PathVariable Long id) { | ||
reservationService.deleteReservation(id); | ||
return ResponseEntity.noContent().build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package roomescape.api; | ||
|
||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
|
||
@Controller | ||
public class StaticPageController { | ||
@GetMapping("/") | ||
public String mainPage() { | ||
return "home"; | ||
} | ||
|
||
|
||
@GetMapping("/reservation") | ||
public String reservationPage() { | ||
return "reservation"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package roomescape.api.dto; | ||
|
||
import jakarta.validation.constraints.NotBlank; | ||
import jakarta.validation.constraints.NotNull; | ||
import roomescape.entity.Person; | ||
import roomescape.entity.Reservation; | ||
import roomescape.util.CustomDateTimeFormat; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalTime; | ||
|
||
public record ReservationRequestDto( | ||
@NotBlank | ||
@NotNull | ||
String name, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
@NotBlank | ||
@NotNull | ||
String date, | ||
@NotBlank | ||
@NotNull | ||
String time | ||
) { | ||
public Reservation toEntity() { | ||
return new Reservation( | ||
new Person(this.name), | ||
LocalDate.parse(date, CustomDateTimeFormat.dateFormatter), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 보통 CustomDateTimeFormat 같은 것은 "hh:MM" 과 같이 특이한 형태일때만 정의하는 것 같아요 |
||
LocalTime.parse(time, CustomDateTimeFormat.timeFormatter) | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package roomescape.api.dto; | ||
import roomescape.entity.Reservation; | ||
import roomescape.util.CustomDateTimeFormat; | ||
|
||
public record ReservationResponseDto( | ||
Long id, | ||
String name, | ||
String date, | ||
String time | ||
) { | ||
public static ReservationResponseDto fromEntity(Reservation reservation) { | ||
if (reservation == null) { | ||
return null; | ||
} | ||
|
||
return new ReservationResponseDto( | ||
reservation.getId(), | ||
reservation.getPerson().getName(), | ||
reservation.getDate().format(CustomDateTimeFormat.dateFormatter), | ||
reservation.getTime().format(CustomDateTimeFormat.timeFormatter) | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package roomescape.entity; | ||
|
||
public class Person { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 클래스를 사용하게 된 이유는 어떤 것일까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 처음에 미션을 읽었을때 Person또한 데이터베이스에 �테이블로 등록 될거라는 생각에 만들었는데 결국 사용하지 않더군요... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 이런 경우에는 사용하지 않는 것을 추천드려요! |
||
private String name; | ||
|
||
public Person(String name) { | ||
this.name = name; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package roomescape.entity; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalTime; | ||
import java.util.Objects; | ||
|
||
public class Reservation { | ||
private Long id; | ||
private Person person; | ||
private LocalDate date; | ||
private LocalTime time; | ||
|
||
public Reservation(Long id, Person person, LocalDate date, LocalTime time) { | ||
this.id = id; | ||
this.person = person; | ||
this.date = date; | ||
this.time = time; | ||
} | ||
public Reservation(Person person, LocalDate date, LocalTime time) { | ||
this.id = 0L; | ||
this.person = person; | ||
this.date = date; | ||
this.time = time; | ||
} | ||
|
||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public Person getPerson() { | ||
return person; | ||
} | ||
|
||
public LocalDate getDate() { | ||
return date; | ||
} | ||
|
||
public LocalTime getTime() { | ||
return time; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (o == null || getClass() != o.getClass()) return false; | ||
Reservation that = (Reservation) o; | ||
return Objects.equals(id, that.id); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 id 만 같으면 같은 예약이다라고 둔 이유가 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DB에서는 id(PK)는 중복될 수 없다. 즉 하나의 테이블을 표현하는 Spring 객체에서 id가 같다면 두 객체는 같은 테이블의 같은 데이터를 표현해주고 있는 것이다. id 필드 말고 다른 필드 또한 equals, hashcode함수에 함께 사용해서 비교할 수 있지 않을까?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://www.korecmblog.com/blog/jpa-equals-and-history 1번의 영감을 받은 블로그입니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dm 으로 왔다갔다 했던 내용인데, Set 이나 HashSet 에 넣는 경우에는 id 를 재정의 하지만, 그 외에는 하지 않는 것이 좋다고 생각하고요 이렇게 하게 된 이유는 추후에 어떻게 변경될지 모른다는 점이 가장 큰데요 |
||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hashCode(id); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package roomescape.exception; | ||
|
||
public class BusinessException extends RuntimeException { | ||
public BusinessException(String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package roomescape.exception; | ||
|
||
public class EntityNotFoundException extends BusinessException { | ||
public EntityNotFoundException(String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package roomescape.exception; | ||
|
||
import org.slf4j.Logger; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.MethodArgumentNotValidException; | ||
import org.springframework.web.bind.annotation.ControllerAdvice; | ||
import org.springframework.web.bind.annotation.ExceptionHandler; | ||
import org.springframework.web.context.request.WebRequest; | ||
import roomescape.util.LoggerUtils; | ||
|
||
import java.util.stream.Collectors; | ||
|
||
@ControllerAdvice | ||
public class GlobalExceptionHandler { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 프로젝트에서 공통적으로 사용되는 BusinessException 을 잡아주는 친구도 있으면 좋을 것 같아요! |
||
private final Logger logger = LoggerUtils.logger(GlobalExceptionHandler.class); | ||
|
||
@ExceptionHandler(IllegalArgumentException.class) | ||
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e, WebRequest request) { | ||
logger.info(e.getMessage()); | ||
return ResponseEntity.badRequest().body(e.getMessage()); | ||
} | ||
|
||
@ExceptionHandler(EntityNotFoundException.class) | ||
public ResponseEntity<String> handleExceptionNotFoundException(EntityNotFoundException e) { | ||
logger.info(e.getMessage()); | ||
return ResponseEntity.badRequest().body(e.getMessage()); | ||
} | ||
|
||
@ExceptionHandler(MethodArgumentNotValidException.class) | ||
public ResponseEntity<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { | ||
String errorFields = e.getBindingResult(). | ||
getFieldErrors() | ||
.stream() | ||
.map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. default message 가 있다면 이 부분을 직접 정의해도 좋을 것 같아요 |
||
.collect(Collectors.joining(", ")); | ||
|
||
logger.info(errorFields); | ||
return ResponseEntity.badRequest().body(errorFields); | ||
} | ||
|
||
@ExceptionHandler(BusinessException.class) | ||
public ResponseEntity<String> handleBusinessException(BusinessException e) { | ||
logger.warn(e.getMessage()); | ||
return ResponseEntity.internalServerError().body(e.getMessage()); | ||
} | ||
|
||
@ExceptionHandler(Exception.class) | ||
public ResponseEntity<String> handleException(Exception e) { | ||
logger.error(e.getMessage()); | ||
return ResponseEntity.internalServerError().body(e.getMessage()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jpa 를 기준으로 보면, save 시점에 알아서 값을 추가해주게 됩니다!
find 를 했을 때는 자동으로 id 를 채워진 상태로 오기 때문에, id 를 직접 set 하는 경우는 거의 없다고 보셔도 될 것 같아요!