-
Notifications
You must be signed in to change notification settings - Fork 134
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] 남해윤 미션 제출합니다. #379
base: haeyoon1
Are you sure you want to change the base?
Changes from all commits
ad9fc14
7dfd8c5
3452ba4
7b379b3
dec3a7a
bad771c
9f4ec62
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 |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package roomescape.controller; | ||
|
||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
|
||
@Controller | ||
public class HomeController { | ||
|
||
@RequestMapping("/") | ||
public String home() { | ||
return "home"; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package roomescape.controller; | ||
|
||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.DeleteMapping; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.ResponseBody; | ||
import roomescape.dto.ReservationRequestDto; | ||
import roomescape.dto.ReservationResponseDto; | ||
import roomescape.service.ReservationService; | ||
|
||
import java.net.URI; | ||
import java.util.List; | ||
|
||
@Controller | ||
public class ReservationController { | ||
|
||
private final ReservationService reservationService; | ||
|
||
public ReservationController(ReservationService reservationService) { | ||
this.reservationService = reservationService; | ||
} | ||
|
||
// 홈화면 | ||
@GetMapping("/reservation") | ||
public String reservationPage() { | ||
return "reservation"; | ||
} | ||
|
||
//예약 조회 | ||
@ResponseBody | ||
@GetMapping("/reservations") | ||
public ResponseEntity<List<ReservationResponseDto>> list() { | ||
List<ReservationResponseDto> reservations = reservationService.getAllReservations(); | ||
return ResponseEntity.ok(reservations); | ||
} | ||
|
||
//예약 추가 | ||
@ResponseBody | ||
@PostMapping("/reservations") | ||
public ResponseEntity<ReservationResponseDto> create(@RequestBody ReservationRequestDto newReservationDto) { | ||
|
||
ReservationResponseDto reservation = reservationService.createReservation(newReservationDto); | ||
|
||
return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())) | ||
.body(reservation); | ||
} | ||
|
||
//예약 삭제 | ||
@DeleteMapping("/reservations/{id}") | ||
public ResponseEntity<Void> delete(@PathVariable Long id) { | ||
reservationService.deleteReservation(id); | ||
return ResponseEntity.noContent().build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package roomescape.dao; | ||
|
||
import org.springframework.jdbc.core.JdbcTemplate; | ||
import org.springframework.stereotype.Repository; | ||
import roomescape.entity.Reservation; | ||
|
||
import java.util.List; | ||
|
||
@Repository | ||
public class ReservationDao { | ||
|
||
private final JdbcTemplate jdbcTemplate; | ||
|
||
public ReservationDao(JdbcTemplate jdbcTemplate) { | ||
this.jdbcTemplate = jdbcTemplate; | ||
} | ||
|
||
public List<Reservation> findAll(){ | ||
String sql = "SELECT id, name, date, time FROM reservation"; | ||
|
||
return jdbcTemplate.query( | ||
sql, (resultSet, rowNum) -> new Reservation( | ||
resultSet.getLong("id"), | ||
resultSet.getString("name"), | ||
resultSet.getString("date"), | ||
resultSet.getTimestamp("time").toLocalDateTime() | ||
)); | ||
} | ||
|
||
public Reservation insert(Reservation reservation) { | ||
String sql = "INSERT INTO reservation(name, date, time) VALUES (?, ?, ?)"; | ||
jdbcTemplate.update(sql, reservation.getName(), reservation.getDate(), reservation.getTime()); | ||
|
||
String query = "SELECT id FROM reservation ORDER BY id DESC LIMIT 1"; | ||
Long id = jdbcTemplate.queryForObject(query, Long.class); | ||
|
||
return new Reservation(id, reservation.getName(), reservation.getDate(), reservation.getTime()); | ||
} | ||
|
||
public void delete(Long id) { | ||
String sql = "DELETE FROM reservation WHERE id = ?"; | ||
jdbcTemplate.update(sql, id); | ||
} | ||
Comment on lines
+40
to
+43
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. 보통 delete 를 해야 하는 경우에 실제 delete 를 하는 경우는 거의 없는데요 조회를 할 때는 진짜 전체를 조회하거나, 현재 활성화 되어있는 전체를 조회하는 방향으로 진행하게 됩니다 soft delete 를 왜 해야 하는지에 대해서도 정리해서 댓글에 달아주시면 공부하는 과정에 많은 도움이 될 것 같아요 |
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package roomescape.dto; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
public class ReservationRequestDto { | ||
private String name; | ||
private String date; | ||
private LocalDateTime time; | ||
|
||
public ReservationRequestDto(String name, String date, LocalDateTime time) { | ||
this.name = name; | ||
this.date = date; | ||
this.time = time; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public String getDate() { | ||
return date; | ||
} | ||
|
||
public LocalDateTime getTime() { | ||
return time; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package roomescape.dto; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
|
||
public class ReservationResponseDto { | ||
private Long id; | ||
private String name; | ||
private String date; | ||
private LocalDateTime time; | ||
|
||
public ReservationResponseDto(Long id, String name, String date, LocalDateTime time) { | ||
this.id = 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. 이 필드의 유무에 따라서 dto 를 분리하는지 아닌지가 나뉠겁니다 적어도 이제 각 목적별로는 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. 예를 들면 id랑 name만 필요한경우, id, name, date만 필요한경우, id, name, date, time모두 필요한 경우가 있다면, 세개의 Dtoclass를 만들어서 해당 경우에 필요한 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. 넵 맞습니다! ex) 만약 date 를 바꿀 수 있는 경우는 관리자밖에 없다 만약 이런 케이스가 아니라 단순히 date 가 있을 수도 있고, 없을 수도 있다 정도의 레벨에서는 같이 두고, nullable 하게 처리하는 것도 괜찮은 방법입니다! 타입 지정은 완벽하게 정답이 있기 보다는 프로젝트의 성숙도에 따라서 더 달라지는 부분이라서 예시 상황마다 다를 것 같아요 |
||
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 LocalDateTime getTime() { | ||
return time; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package roomescape.entity; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
public class Reservation { | ||
private Long id; | ||
private String name; | ||
private String date; | ||
private LocalDateTime time; | ||
|
||
public Reservation(Long id, String name, String date, LocalDateTime time) { | ||
this.id = id; | ||
this.name = name; | ||
this.date = date; | ||
this.time = time; | ||
} | ||
|
||
public Reservation(String name, String date, LocalDateTime time) { | ||
this(null, name, date, time); | ||
} | ||
|
||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public String getDate() { | ||
return date; | ||
} | ||
|
||
public LocalDateTime getTime() { | ||
return time; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package roomescape.exception; | ||
|
||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
|
||
@RestControllerAdvice | ||
public class ExceptionHandler { | ||
|
||
@org.springframework.web.bind.annotation.ExceptionHandler(NotFoundReservationException.class) | ||
public ResponseEntity<String> handleNotFoundReservationException(NotFoundReservationException ex) { | ||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage()); | ||
} | ||
|
||
@org.springframework.web.bind.annotation.ExceptionHandler(InvalidValueException.class) | ||
public ResponseEntity<String> handleInvalidReservationException(InvalidValueException ex) { | ||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package roomescape.exception; | ||
|
||
public class InvalidValueException extends RuntimeException { | ||
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. 이 프로젝트에서 다루는 공통 예외가 있으면 어떨까요? 그래서 모든 예외는 저 RoomscapeException 을 상속하는 구조로 만들어지는거죠 ControllerAdvice 쪽에서도 RoomscapeException 를 처리하는 ExceptionHandler 와 Exception 을 처리하는 ExceptionHandler 을 서로 분리해서 서로 다른 처리를 할 수 있을 것이고요 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. 이미 InvalidValueException랑 NotFoundReservationException는 RuntimeException를 상속 받고 있는데 그러면 RuntimeException 대신 새로 만든 RoomscapeException를 상속받는 클래스로 수정하라는 말씀이신가요?
그리고 이 부분이 잘 이해가 되지 않습니다..!ㅠㅠ ControllerAdvice는 사용하지 않은 것 같은데.... 설명부탁드립니다! 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.
graph TD;
InvalidValueException --> RoomscapeException --> RuntimeException
NotFoundReservationException --> RoomscapeException
와 같은 형태가 되면 가장 이상적일 것 같아요!
제가 말씀드린 ControllerAdvice = RestControllerAdvice 입니다! |
||
|
||
public InvalidValueException(String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package roomescape.exception; | ||
|
||
public class NotFoundReservationException extends RuntimeException { | ||
public NotFoundReservationException(String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,34 @@ | ||||||
package roomescape.repository; | ||||||
|
||||||
import org.springframework.http.ResponseEntity; | ||||||
import org.springframework.jdbc.core.JdbcTemplate; | ||||||
import org.springframework.stereotype.Repository; | ||||||
import roomescape.dao.ReservationDao; | ||||||
import roomescape.entity.Reservation; | ||||||
|
||||||
import java.util.List; | ||||||
|
||||||
@Repository | ||||||
public class ReservationRepository { | ||||||
|
||||||
private final ReservationDao reservationDao; | ||||||
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. 보통 dao 의 네이밍은
Suggested change
와 같이 어떤 클래스를 사용했는지를 명확하게 적어주는 편이 좋은 것 같더라고요 repository 와 dao 가 나온 이유도 이와 조금 더 맞을 것 같은데요 |
||||||
|
||||||
public ReservationRepository(ReservationDao reservationDao) { | ||||||
this.reservationDao = reservationDao; | ||||||
} | ||||||
|
||||||
public List<Reservation> findAll() { | ||||||
return reservationDao.findAll(); | ||||||
} | ||||||
|
||||||
//예약 추가 | ||||||
public Reservation insert(Reservation reservation) { | ||||||
return reservationDao.insert(reservation); | ||||||
} | ||||||
|
||||||
//예약 삭제 | ||||||
public void delete(Long id) { | ||||||
reservationDao.delete(id); | ||||||
} | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package roomescape.service; | ||
|
||
import org.springframework.stereotype.Service; | ||
import roomescape.dto.ReservationRequestDto; | ||
import roomescape.dto.ReservationResponseDto; | ||
import roomescape.entity.Reservation; | ||
import roomescape.repository.ReservationRepository; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
@Service | ||
public class ReservationService { | ||
|
||
private final ReservationRepository reservationRepository; | ||
|
||
public ReservationService(ReservationRepository reservationRepository) { | ||
this.reservationRepository = reservationRepository; | ||
} | ||
|
||
public List<ReservationResponseDto> getAllReservations(){ | ||
return reservationRepository.findAll() | ||
.stream() | ||
.map(this::toResponseDto) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
public ReservationResponseDto createReservation(ReservationRequestDto requestDto){ | ||
|
||
Reservation reservation = new Reservation( | ||
requestDto.getName(), | ||
requestDto.getDate(), | ||
requestDto.getTime() | ||
); | ||
Reservation savedReservation = reservationRepository.insert(reservation); | ||
return toResponseDto(savedReservation); | ||
} | ||
|
||
public void deleteReservation(Long id){ | ||
reservationRepository.delete(id); | ||
} | ||
|
||
private ReservationResponseDto toResponseDto(Reservation reservation) { | ||
return new ReservationResponseDto( | ||
reservation.getId(), | ||
reservation.getName(), | ||
reservation.getDate(), | ||
reservation.getTime() | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
server.port=8080 | ||
spring.h2.console.enabled=true | ||
spring.h2.console.path=/h2-console | ||
spring.datasource.url=jdbc:h2:mem:database |
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, | ||
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. 여기도 VARCHAR 형태가 아닌 TimeStamp 형태로 바꿔보면 어떨까요? 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. 이번에는 date 와 time 을 한번 통일시켜보면 어떨까요? |
||
time Timestamp NOT NULL, | ||
PRIMARY KEY (id) | ||
); |
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.
저는 개인적으로 이렇게 메소드 주석보다는 클래스 레벨에 어떤 책임을 가지고 있는지 적는 것을 선호합니다
코드 레벨에서 당연히 바로 알 수 있는 내용일 경우에 전체 주석의 신뢰도를 떨어뜨려서 오히려 보기 힘들 수도 있는 악영향을 끼칠 수도 있거든요