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 Core] 신혜빈 미션 제출합니다. #385

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1e79b43
4단계 테스트 통과 완료
shin378378 Nov 6, 2024
ff97542
최종과제 제출
shin378378 Nov 6, 2024
19ab3d6
초기상태 설정
shin378378 Nov 12, 2024
64a4cb5
feat : 데이터 베이스 스키마 생성하기
shin378378 Nov 12, 2024
12746cc
feat : 데이터 조회기능 생성하기
shin378378 Nov 12, 2024
8258322
feat : 데이터 추가, 삭제하는 기능 생성하기
shin378378 Nov 12, 2024
299e89f
최종 테스트 통과 완료
shin378378 Nov 13, 2024
a1d42a0
feat : test 3 통과시키기
shin378378 Nov 20, 2024
60ab85b
refactor : mvc 피드백 받아 수정
shin378378 Nov 20, 2024
9c8e2b2
refactor : mvc 피드백 모두 반영
shin378378 Nov 20, 2024
b1710ee
refactor : reservationService 수정하기
shin378378 Nov 21, 2024
e8010d2
refactor : NamedParameterJdbcTemplate 기능적용
shin378378 Nov 21, 2024
f7c72e6
refactor : simpleJdbcInsert를 활용해 예약 추가하기
shin378378 Nov 21, 2024
7ed438e
feat : 8단계 요구사항 추가
shin378378 Nov 25, 2024
b122972
feat : 과목 추가 500 오류 못 찾음 ㅜㅠ
shin378378 Nov 25, 2024
02664e0
feat : @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) 붙여 객체 저장 문제 해결
shin378378 Nov 25, 2024
48c01ca
feat : Time으로 바꾸기
shin378378 Nov 26, 2024
07aa2e1
feat : Time으로 바꾸기
shin378378 Nov 26, 2024
c6a0084
feat : 쿼리문 바꾸기
shin378378 Nov 26, 2024
8a7a8df
feat : 9단계 400에러 코드 추가
shin378378 Nov 26, 2024
3c43a85
feat : 10단계 테스트 코드 추가
shin378378 Nov 26, 2024
1a9c9cd
10단계 테스트 통과
shin378378 Nov 26, 2024
e9e0ea5
reservation에서 String Time을 Long timeId로 수정하기
shin378378 Nov 27, 2024
55618a6
requestDto에서 timeId를 time으로 수정
shin378378 Nov 27, 2024
e12ed5f
값 출력 성공!!
shin378378 Nov 27, 2024
2b8570e
test 9 단계 통과
shin378378 Nov 27, 2024
7484261
sql 로직수정
shin378378 Nov 28, 2024
083491c
시간 숫자로 나옴 ㅜㅠ
shin378378 Nov 28, 2024
954d93b
refactor : 주석 없애기
shin378378 Nov 28, 2024
1459097
refactor : 자바 time 기능을 time 객체로 바꾸기
shin378378 Dec 23, 2024
a2fab69
refactor : 숫자가 아닌 경우에도 에러처리를 할 수 있도록 만들기
shin378378 Dec 24, 2024
3649fe4
refactor : reservation dao 만들기
shin378378 Dec 31, 2024
55c98d4
refactor : time dao 만들기
shin378378 Dec 31, 2024
5a525af
feat : 테스트 순서대로 실행되도록 설정해두기
shin378378 Jan 1, 2025
95a14c1
refactor : 테스트 격리
shin378378 Jan 1, 2025
4fceeb0
Add database file to .gitignore and remove it from version control
shin378378 Jan 1, 2025
cf8d126
Add data/database.mv.db to .gitignore and remove it from version control
shin378378 Jan 1, 2025
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ build/
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
data/


### IntelliJ IDEA ###
.idea
Expand Down
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ group = 'nextstep'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'



repositories {
mavenCentral()
}
Expand All @@ -16,6 +18,13 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'
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'
implementation 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}

test {
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/roomescape/PageController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package roomescape;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class PageController {

@GetMapping("/reservation")
public String readReservationPage(Model model) {
return "new-reservation";
}

@GetMapping("/time")
public String readTimePage(Model model) {
return "time";
}
}
1 change: 0 additions & 1 deletion src/main/java/roomescape/RoomescapeApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ public class RoomescapeApplication {
public static void main(String[] args) {
SpringApplication.run(RoomescapeApplication.class, args);
}

}
13 changes: 13 additions & 0 deletions src/main/java/roomescape/RoomescapeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package roomescape;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class RoomescapeController {
@GetMapping("/")
public String homePage() {
return "home";
}
}

59 changes: 59 additions & 0 deletions src/main/java/roomescape/exception/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package roomescape.exception;

import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException e) {
String errorMessage = e.getBindingResult().getFieldErrors().stream()
.map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
.reduce((msg1, msg2) -> msg1 + ", " + msg2)
.orElse("Invalid request");
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorMessage);
}

@ExceptionHandler(MissingRequiredFieldException.class)
public ResponseEntity<String> handleMissingRequiredFieldException(MissingRequiredFieldException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}

@ExceptionHandler(NotFoundReservationException.class)
public ResponseEntity<String> handleNotFoundReservationException(NotFoundReservationException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}

// @ExceptionHandler(HttpMessageNotReadableException.class)
// public ResponseEntity<String> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
// return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("잘못된 데이터 형식이 입력되었습니다. time은 숫자여야 합니다.");
// }

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<String> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
if (e.getCause() instanceof InvalidFormatException cause &&
cause.getPathReference().contains("time")) {
return ResponseEntity.badRequest()
.body("time은 숫자여야 합니다.");
}
return ResponseEntity.badRequest()
.body("잘못된 요청 데이터 형식입니다.");
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}

@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGlobalException(Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("에러발생: " + e.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package roomescape.exception;

public class MissingRequiredFieldException extends RuntimeException {
public MissingRequiredFieldException(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);
}
}
34 changes: 34 additions & 0 deletions src/main/java/roomescape/reservationManage/Reservation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package roomescape.reservationManage;


import roomescape.timeManage.Time;

public class Reservation {
private Long id;
private String name;
private String date;
private Time time;

public Reservation(Long id, String name, String date, Time 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 Time getTime() {
return time;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package roomescape.reservationManage;

import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import roomescape.reservationManage.dto.AddReservationRequest;

import java.util.List;

@RestController
@RequestMapping("/reservations")
public class ReservationApiController {
private final ReservationService reservationService;

public ReservationApiController(ReservationService reservationService) {
this.reservationService = reservationService;
}

@GetMapping
public ResponseEntity<List<Reservation>> getReservations() {
List<Reservation> reservations = reservationService.getAllReservations();
return ResponseEntity.ok().body(reservations);
}

@PostMapping
public ResponseEntity<Reservation> addReservation(@RequestBody @Valid AddReservationRequest reservationRequest) {
Reservation reservation = reservationService.addReservation(
reservationRequest.getName(),
reservationRequest.getDate(),
reservationRequest.getTime()
);
return ResponseEntity.status(HttpStatus.CREATED).body(reservation);
}

@DeleteMapping("/{id}")
public ResponseEntity<String> deleteReservation(@PathVariable Long id) {
reservationService.cancelReservation(id);
return ResponseEntity.noContent().build();
}
}
66 changes: 66 additions & 0 deletions src/main/java/roomescape/reservationManage/ReservationDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package roomescape.reservationManage;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Component;
import roomescape.timeManage.Time;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class ReservationDao {
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public ReservationDao(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}

public Reservation insert(String name, String date, Long time) {
SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(namedParameterJdbcTemplate.getJdbcTemplate())
.withTableName("reservation")
.usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("name", name);
parameters.put("date", date);
parameters.put("time_id", time);

Number generatedKey = simpleJdbcInsert.executeAndReturnKey(parameters);
return findById(generatedKey.longValue());
}

public Reservation findById(Long id) {
String sql = "SELECT r.id AS reservation_id, r.name, r.date, t.id AS time_id, t.time AS time_value " +
"FROM reservation AS r INNER JOIN time AS t ON r.time_id = t.id WHERE r.id = :id";
MapSqlParameterSource params = new MapSqlParameterSource("id", id);
return namedParameterJdbcTemplate.queryForObject(sql, params, reservationRowMapper);
}

public void deleteById(Long id) {
String sql = "DELETE FROM reservation WHERE id = :id";
MapSqlParameterSource params = new MapSqlParameterSource("id", id);
namedParameterJdbcTemplate.update(sql, params);
}

public List<Reservation> findAll() {
String sql = "SELECT r.id AS reservation_id, r.name, r.date, t.id AS time_id, t.time AS time_value " +
"FROM reservation AS r INNER JOIN time AS t ON r.time_id = t.id";
return namedParameterJdbcTemplate.query(sql, reservationRowMapper);
}

private final RowMapper<Reservation> reservationRowMapper = (rs, rowNum) -> {
Time time = new Time(
rs.getLong("time_id"),
rs.getString("time_value")
);
return new Reservation(
rs.getLong("reservation_id"),
rs.getString("name"),
rs.getString("date"),
time
);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package roomescape.reservationManage.repository;

import org.springframework.stereotype.Repository;
import roomescape.exception.NotFoundReservationException;
import roomescape.reservationManage.Reservation;
import roomescape.reservationManage.ReservationDao;

import java.util.List;

@Repository
public class ReservationRepository {
private final ReservationDao reservationDao;

public ReservationRepository(ReservationDao reservationDao) {
this.reservationDao = reservationDao;
}

public List<Reservation> findAll() {
return reservationDao.findAll();
}

public Reservation save(String name, String date, Long time) {
return reservationDao.insert(name, date, time);
}

public Reservation findById(Long id) {
Reservation reservation = reservationDao.findById(id);
if (reservation == null) {
throw new NotFoundReservationException("예약 ID가 존재하지 않습니다: " + id);
}
return reservation;
}

public void delete(Long id) {
reservationDao.deleteById(id);
}
}
30 changes: 30 additions & 0 deletions src/main/java/roomescape/reservationManage/ReservationService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package roomescape.reservationManage;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import roomescape.reservationManage.repository.ReservationRepository;

import java.util.List;

@Service
public class ReservationService {
private final ReservationRepository reservationRepository;

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

public List<Reservation> getAllReservations() {
return reservationRepository.findAll();
}

@Transactional
public Reservation addReservation(String name, String date, Long time) {
return reservationRepository.save(name, date, time);
}

@Transactional
public void cancelReservation(Long id) {
reservationRepository.delete(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package roomescape.reservationManage.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public class AddReservationRequest {
@NotBlank(message = "이름은 필수 값입니다.")

Choose a reason for hiding this comment

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

적용하려고 노력하신 모습이 보여서 정말 좋은 것 같아요! 👍
예전에 코드를 찾아볼 수 없을 정도로 많이 발전하셨네요

Copy link
Author

Choose a reason for hiding this comment

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

감사합니다!! :) 리뷰어님 덕분입니당!!

final private String name;
@NotBlank(message = "날짜는 필수 값입니다.")
final private String date;
@NotNull(message = "timeId는 필수 값입니다.")
final private Long time;

public AddReservationRequest(String name, String date, Long time) {
this.name = name;
this.date = date;
this.time = time;
}

public String getName() {
return name;
}

public String getDate() {
return date;
}

public Long getTime() {
return time;
}
}
Loading