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] 안금서 미션 제출합니다. #373

Open
wants to merge 44 commits into
base: goldm0ng
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
d888ea0
gradle 의존성 추가
goldm0ng Nov 6, 2024
7adbc1c
<Add> 어드민 페이지 응답 구현
goldm0ng Nov 6, 2024
a7e7b1c
<Add> 핵심 도메인 Reservation 추가
goldm0ng Nov 6, 2024
5ffe0d1
<Add> 컨트롤러 ReservationController 추가
goldm0ng Nov 6, 2024
3fce7dd
<Add> 필수 예약 데이터 누락 관련 예외 MissingReservationDataException 추가
goldm0ng Nov 6, 2024
4e5b6c3
<Add> 예약 찾기 관련 예외 NotFoundReservationException 추가
goldm0ng Nov 6, 2024
45a3092
<Add> 에외 관리 관련 GlobalExceptionHandler 추가
goldm0ng Nov 6, 2024
87cd948
<Add> 미션 단계별 테스트 추가
goldm0ng Nov 6, 2024
f2ceebe
<FIX> controller pakage 추가 및 ReservationController 수정
goldm0ng Nov 13, 2024
0ce07cb
<FIX> domain package 추가 및 도메인 클래스 수정
goldm0ng Nov 13, 2024
b80921f
<ADD> dto package 추가 및 예약 관련 DTO 추가
goldm0ng Nov 13, 2024
b5096b7
<DELETE> 유효성 검사 방법 변경에 따른 사용자 정의 예외 클래스 삭제 및 Handler 재구성
goldm0ng Nov 13, 2024
272e633
<ADD> dto 내에서 유효성 검사를 하기 위한 의존성 추가
goldm0ng Nov 13, 2024
e3ce304
<FIX> 외부에서 예외메세지를 전달하는 방식에서 기본 예외 메세지를 가지고 있는 방식으로 변경
goldm0ng Nov 13, 2024
2855044
<ADD> 예외처리 방식 일부 변경에 따른 Reservation에 관한 예외핸들러 추가
goldm0ng Nov 13, 2024
4c61b9a
-
goldm0ng Nov 13, 2024
3262ef2
<FIX> 도메인 필드 내 final 삭제
goldm0ng Nov 13, 2024
8254291
<FIX> Dto 생성자 추가
goldm0ng Nov 13, 2024
b747795
<ADD> 예약 관련 Repository 인터페이스 추가
goldm0ng Nov 13, 2024
19e40f1
<ADD> 메모리 기반 데이터 관리 구현체 추가
goldm0ng Nov 13, 2024
2bf90d0
<ADD> 예약 비즈니스 관련 Service 추가
goldm0ng Nov 13, 2024
51b6b55
<FIX> repository, service 추가에 따른 controller 재구성
goldm0ng Nov 13, 2024
8b552a6
<FIX> DTO->Entity 변환 위치 및 로직 수정
goldm0ng Nov 19, 2024
98eeee3
<FIX> Controller와 RestController 분리
goldm0ng Nov 19, 2024
a293899
<FIX> 클래스 범위 수정, 예외 처리 핸들러 추가 및 패키지 구조 조정
goldm0ng Nov 19, 2024
ee1ec84
<ADD> 페이지 렌더링하는 뷰 컨트롤러에 대한 예외처리 분리
goldm0ng Nov 19, 2024
edf05cd
<FIX> 레이어드 아키텍처에 기반한 패키지 구조 조정
goldm0ng Nov 19, 2024
6a5885b
<ADD> Spring jdbc Starter 의존성 추가
goldm0ng Nov 13, 2024
5712acf
<ADD> DB 설정 추가
goldm0ng Nov 13, 2024
5208497
<ADD> 테이블 스키마 정의
goldm0ng Nov 13, 2024
bbc0aa7
<FIX> 1,2,3,4 단계 테스트 네이밍 수정
goldm0ng Nov 13, 2024
92555ec
<ADD> 5,6,7 단계 테스트 추가
goldm0ng Nov 13, 2024
3157680
<FIX> 도메인 기본 생성자 추가
goldm0ng Nov 13, 2024
03b7b9e
<FIX> 메모리 기반 데이터 저장소에서 @Repository 삭제
goldm0ng Nov 13, 2024
73a68e4
<ADD> h2 기반 데이터 관리 용도의 Repository 구현체 추가
goldm0ng Nov 13, 2024
d3bda44
<FIX> 미션 흐름에 따른 비즈니스 로직 메서드 순서 바꾸기
goldm0ng Nov 13, 2024
0cfa4d4
<FIX> DTO->Entity 변환 위치 및 로직 수정
goldm0ng Nov 19, 2024
1379add
<FIX> 레이어드 아키텍처에 기반한 패키지 구조 조정
goldm0ng Nov 19, 2024
50f9f7d
<FIX> DTO -> Entity 변환 위치 변경에 따른 수정
goldm0ng Nov 22, 2024
7f41fe9
<FIX> 패키지 구조 변경에 따른 수정
goldm0ng Nov 22, 2024
46d524d
<FIX> DTO를 record 타입으로 변경
goldm0ng Nov 22, 2024
66148ce
<FIX> 자동 정렬
goldm0ng Nov 22, 2024
fb00e38
<FIX> 메서드 체이닝 적용
goldm0ng Nov 22, 2024
f29f0c6
<FIX> 예외처리 위치 변경에 따른 수정
goldm0ng Nov 22, 2024
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
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,18 @@ 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'
compileOnly 'org.projectlombok:lombok:1.18.28'
annotationProcessor 'org.projectlombok:lombok:1.18.28'

implementation 'org.springframework.boot:spring-boot-starter-validation'

implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
}

test {
useJUnitPlatform()
}

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

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

@Controller
public class MainPageController {

@GetMapping("/")
public String showHomePage() {
return "home";
}

@GetMapping("/reservation")
public String showReservationForm() {
return "reservation";
}
}
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);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package roomescape.reservation.business;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import roomescape.reservation.domain.Reservation;
import roomescape.reservation.presentation.dto.ReservationDto;
import roomescape.reservation.persistence.ReservationRepository;

import java.util.List;

@Service
@RequiredArgsConstructor
public class ReservationService {

private final ReservationRepository repository;

public Reservation addReservation(ReservationDto reservationDto) {
Reservation reservation = convertToEntity(reservationDto);
return repository.save(reservation);
}

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

public void deleteReservation(Long reservationId) {
repository.delete(reservationId);
}

private Reservation convertToEntity(ReservationDto reservationDto) {
return new Reservation(null, reservationDto.name(), reservationDto.date(), reservationDto.time());
}
}
26 changes: 26 additions & 0 deletions src/main/java/roomescape/reservation/domain/Reservation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package roomescape.reservation.domain;

import lombok.Getter;

@Getter
public class Reservation {

private Long id;
private String name;
private String date;
private String time;

public Reservation() {
}

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

public void setId(Long id) {
this.id = id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package roomescape.reservation.persistence;

import lombok.RequiredArgsConstructor;
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.reservation.domain.Reservation;
import roomescape.reservation.presentation.exception.NotFoundReservationException;

import java.sql.PreparedStatement;
import java.util.List;

@Repository
@RequiredArgsConstructor
public class JdbcReservationRepository implements ReservationRepository {

private final JdbcTemplate jdbcTemplate;

@Override
public Reservation save(Reservation reservation) {
String sql = "insert into reservation (name, date, time) values (?,?,?)";

KeyHolder keyHolder = new GeneratedKeyHolder();

jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"});
ps.setString(1, reservation.getName());
ps.setString(2, reservation.getDate());
ps.setString(3, reservation.getTime());
return ps;
}, keyHolder);

Long generatedAutoId = keyHolder.getKey().longValue();
return new Reservation(generatedAutoId, reservation.getName(), reservation.getDate(), reservation.getTime());
}

@Override
public Reservation findById(Long reservationId) {
String sql = "select id, name, date, time from reservation where id = ?";

try {
return jdbcTemplate.queryForObject(sql, reservationMapper(), reservationId);
} catch (EmptyResultDataAccessException e) {
throw new NotFoundReservationException();
}
}

@Override
public List<Reservation> findAll() {
String sql = "select id, name, date, time from reservation";

return jdbcTemplate.query(sql, reservationMapper());
}

@Override
public void delete(Long reservationId) {
Reservation deletedReservation = this.findById(reservationId);

String sql = "delete from reservation where id = ?";
jdbcTemplate.update(sql, deletedReservation.getId());

}

private RowMapper<Reservation> reservationMapper() {
return ((rs, rowNum) -> {
return new Reservation(
rs.getLong("id"),
rs.getString("name"),
rs.getString("date"),
rs.getString("time")
);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package roomescape.reservation.persistence;

import roomescape.reservation.domain.Reservation;
import roomescape.reservation.presentation.exception.NotFoundReservationException;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

public class MemoryReservationRepository implements ReservationRepository {

private static Map<Long, Reservation> reservationStore = new ConcurrentHashMap<>();
private static AtomicLong index = new AtomicLong(1);

@Override
public Reservation save(Reservation reservation) {

Long reservationId = index.getAndIncrement();
reservation.setId(reservationId);
reservationStore.put(reservationId, reservation);

return reservation;
}

@Override
public Reservation findById(Long reservationId) {
Reservation reservation = reservationStore.get(reservationId);
if (reservation == null) {
throw new NotFoundReservationException();
}
return reservation;
}

@Override
public List<Reservation> findAll() {
return new ArrayList<>(reservationStore.values());
}

@Override
public void delete(Long reservationId) {
Reservation deletedReservation = this.findById(reservationId);
reservationStore.remove(deletedReservation.getId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package roomescape.reservation.persistence;

import roomescape.reservation.domain.Reservation;

import java.util.List;

public interface ReservationRepository {

Reservation save(Reservation reservation);

Reservation findById(Long reservationId);

List<Reservation> findAll();

void delete(Long reservationId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package roomescape.reservation.presentation;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import roomescape.reservation.domain.Reservation;
import roomescape.reservation.presentation.dto.ReservationDto;
import roomescape.reservation.business.ReservationService;

import java.net.URI;
import java.util.List;

@RestController
@RequiredArgsConstructor
public class ReservationController {

private final ReservationService reservationService;

@GetMapping("/reservations")
public ResponseEntity<List<Reservation>> readReservation() {
return ResponseEntity.ok(reservationService.checkReservations());
}

@PostMapping("/reservations")
public ResponseEntity<Reservation> createReservation(@Valid @RequestBody ReservationDto reservationDto) {
Reservation savedReservation = reservationService.addReservation(reservationDto);
return ResponseEntity.created(URI.create("/reservations/" + savedReservation.getId())).body(savedReservation);
}

@DeleteMapping("/reservations/{reservationId}")
public ResponseEntity<Void> deleteReservation(@PathVariable Long reservationId) {
reservationService.deleteReservation(reservationId);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package roomescape.reservation.presentation.dto;

import jakarta.validation.constraints.NotBlank;

public record ReservationDto(
@NotBlank(message = "예약자 이름을 입력하세요.") String name,
@NotBlank(message = "예약 날짜를 입력하세요.") String date,
@NotBlank(message = "예약 시간을 입력하세요.") String time
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package roomescape.reservation.presentation.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import roomescape.MainPageController;

@Slf4j
@ControllerAdvice(assignableTypes = MainPageController.class)
public class MainPageExceptionHandler {
@ExceptionHandler(Exception.class)
public String handleException(Exception e) {
log.error("error: " + e.getMessage());
return "error/500"; //view 렌더링 페이지는 만들지 않음!
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package roomescape.reservation.presentation.exception;

public class NotFoundReservationException extends RuntimeException {

private static final String NOT_FOUND_RESERVATION_MESSAGE = "예악을 찾을 수 없습니다.";

public NotFoundReservationException() {
super(NOT_FOUND_RESERVATION_MESSAGE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package roomescape.reservation.presentation.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import roomescape.reservation.presentation.ReservationController;

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

@Slf4j
@ControllerAdvice(assignableTypes = ReservationController.class)
public class ReservationExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();

for (FieldError error : e.getBindingResult().getFieldErrors()) {
errors.put(error.getField(), error.getDefaultMessage());
log.info("validation error on field {} : {}", error.getField(), error.getDefaultMessage());
}

return ResponseEntity.badRequest().body(errors);
}

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

@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
return ResponseEntity.internalServerError().body(e.getMessage());
}
}
4 changes: 4 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# h2-console ??? ??
spring.h2.console.enabled=true
# db url
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)
);
19 changes: 0 additions & 19 deletions src/test/java/roomescape/MissionStepTest.java

This file was deleted.

Loading