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] 신예린 미션 제출합니다. #255

Open
wants to merge 40 commits into
base: nyeroni
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
92bd86a
feat: starter-web 의존성 추가
nyeroni May 13, 2024
0b72bb7
feat: thymeleaf 의존성 추가
nyeroni May 13, 2024
882f0bb
feat: home 메인 페이지 설정
nyeroni May 13, 2024
f63189e
feat: reservation 화면 설정
nyeroni May 13, 2024
6251646
feat: 2단계 테스트 등록
nyeroni May 13, 2024
26f4a4f
feat: lombok 의존성 추가
nyeroni May 13, 2024
f390405
feat: jpa 의존성 추가
nyeroni May 13, 2024
3c2cea6
feat: jpa 의존성 제거
nyeroni May 13, 2024
1e5ad44
feat: Reservation 구현
nyeroni May 13, 2024
d0b93d4
feat: ReservationRepository 구현
nyeroni May 13, 2024
dba44ed
feat: 예약 전체 조회 생성
nyeroni May 13, 2024
bfac22a
test: 예약 건수 개수 테스트 코드
nyeroni May 13, 2024
e61c095
feat: DTO로 반환 형식 변경
nyeroni May 13, 2024
5374dcd
feat: DTO로 반환 형식 변경
nyeroni May 13, 2024
b491654
feat: service 생성
nyeroni May 13, 2024
df43e64
feat: findAll 수정
nyeroni May 13, 2024
1945b54
feat: add, delete 구현
nyeroni May 13, 2024
f5ac7f0
feat: 3단계 테스트
nyeroni May 13, 2024
236ef81
feat: 예외처리 및 테스트
nyeroni May 13, 2024
9dac830
refactor: 리팩토링
nyeroni May 14, 2024
29efa97
refactor: 리팩토링
nyeroni May 14, 2024
757fd81
feat: Jdbc, H2 의존성 추가
nyeroni May 20, 2024
bcd7982
feat: h2 설정
nyeroni May 20, 2024
2727b15
feat: h2 url 변경
nyeroni May 20, 2024
131bb78
test: test 코드 설정
nyeroni May 20, 2024
1222d95
feat: schema 설정
nyeroni May 20, 2024
a99cd04
feat: 패키지 이동
nyeroni May 20, 2024
37f3451
feat: 패키지 이동
nyeroni May 20, 2024
68d10ff
feat: RepositoryImpl 생성
nyeroni May 20, 2024
05960f2
feat: id atomicLong 사용
nyeroni May 20, 2024
e751780
feat: save 추가
nyeroni May 20, 2024
5bfb047
feat: @Override 설정
nyeroni May 20, 2024
24cc815
feat: jdbc 사용
nyeroni May 20, 2024
f662620
feat: jdbc repository로 변경
nyeroni May 20, 2024
a06dd35
test: 6단계 test
nyeroni May 20, 2024
2729ac3
feat: findById 예외처리
nyeroni May 20, 2024
48b0bd9
feat: 취소할 문구 제거
nyeroni May 20, 2024
0a79912
test: 7단계 test
nyeroni May 20, 2024
53a3367
refactor: private로 변경
nyeroni May 20, 2024
2eee21d
refactor: @Primary 사용
nyeroni May 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 @@ -14,8 +14,18 @@ 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'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'
testImplementation 'org.projectlombok:lombok'
}

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

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

@Controller
public class HomeController {

@GetMapping("/")
public String home() {
return "home";
}
}
46 changes: 46 additions & 0 deletions src/main/java/roomescape/controller/ReservationApiController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package roomescape.controller;


import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import roomescape.dto.ReservationRequest;
import roomescape.dto.ReservationResponse;
import roomescape.exception.BadRequestException;
import roomescape.service.ReservationService;

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

@RestController
@RequiredArgsConstructor
public class ReservationApiController {

private final ReservationService reservationService;

@GetMapping("/reservations")
public ResponseEntity<List<ReservationResponse>> reservations(){
List<ReservationResponse> responseDtoList = reservationService.findAllReservations();

return ResponseEntity.ok(responseDtoList);
}

@PostMapping("/reservations")
public ResponseEntity<ReservationResponse> addReservation(@RequestBody ReservationRequest reservationRequest){
reservationRequest.validate();
ReservationResponse responseDto = reservationService.addReservation(reservationRequest);
return ResponseEntity.created(URI.create("/reservations/" + responseDto.getId()))
.body(responseDto);
}

@DeleteMapping("/reservations/{id}")
public ResponseEntity<Void> cancelReservation(@PathVariable Long id){
reservationService.cancelReservation(id);
return ResponseEntity.noContent().build();
}

@ExceptionHandler(BadRequestException.class)
public ResponseEntity<String> handleBadRequestException(BadRequestException ex) {
return ResponseEntity.badRequest().body(ex.getMessage());
}
}
13 changes: 13 additions & 0 deletions src/main/java/roomescape/controller/ReservationController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package roomescape.controller;

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

@Controller
public class ReservationController {

@GetMapping("/reservation")
public String reservation() {
return "reservation";
}
}
16 changes: 16 additions & 0 deletions src/main/java/roomescape/domain/Reservation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package roomescape.domain;

import lombok.Data;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

@Data
public class Reservation {
private Long id;
private String name;
private LocalDate date;
private LocalTime time;

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

import lombok.Getter;
import lombok.Setter;
import roomescape.exception.BadRequestException;

import java.time.LocalDate;
import java.time.LocalTime;

@Getter
@Setter
public class ReservationRequest {
private String name;
private LocalDate date;
private LocalTime time;

public void validate() {
if (name == null || name.isEmpty()) {
throw new BadRequestException("예약 이름은 필수 입력값입니다.");
}

if (date == null) {
throw new BadRequestException("예약 날짜는 필수 입력값입니다.");
}

if (time == null) {
throw new BadRequestException("예약 시간은 필수 입력값입니다.");
}
}

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

import lombok.Getter;
import lombok.Setter;
import roomescape.domain.Reservation;

@Setter
@Getter
public class ReservationResponse {

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

public static ReservationResponse from(Reservation reservation) {
ReservationResponse dto = new ReservationResponse();
dto.setId(reservation.getId());
dto.setName(reservation.getName());
dto.setDate(reservation.getDate().toString());
dto.setTime(reservation.getTime().toString());
return dto;
}
}
11 changes: 11 additions & 0 deletions src/main/java/roomescape/exception/BadRequestException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package roomescape.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException{
public BadRequestException(String message){
super(message);
}
}
71 changes: 71 additions & 0 deletions src/main/java/roomescape/repository/ReservationJdbcRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package roomescape.repository;

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.domain.Reservation;

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

@Repository
@RequiredArgsConstructor
public class ReservationJdbcRepository implements ReservationRepositoryImpl{

private final JdbcTemplate jdbcTemplate;

private final RowMapper<Reservation> reservationRowMapper = (rs, rowNum) -> {
Reservation reservation = new Reservation();
reservation.setId(rs.getLong("id"));
reservation.setName(rs.getString("name"));
reservation.setDate(rs.getDate("date").toLocalDate());
reservation.setTime(rs.getTime("time").toLocalTime());
return reservation;
};

@Override
public List<Reservation> findAll() {
String sql = "SELECT * FROM reservation";
return jdbcTemplate.query(sql, reservationRowMapper);
}

@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, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, reservation.getName());
ps.setDate(2, java.sql.Date.valueOf(reservation.getDate()));
ps.setTime(3, java.sql.Time.valueOf(reservation.getTime()));
return ps;
}, keyHolder);

Long generatedId = keyHolder.getKey().longValue();
reservation.setId(generatedId);

Choose a reason for hiding this comment

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

도메인 reservation은 모든 필드에 setter가 있기 때문에 저장소 뿐만 아니라 어디에서도 필드에 접근하여 다른 값으로 변경 할 수 있네요.
예린님은 setter의 사용에 대해서 어떻게 생각하시는지 궁금합니다 :)

Copy link
Author

Choose a reason for hiding this comment

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

원랜 엔티티에서 setter를 사용하지 않는데, 이번엔 잘못 넣은 것 같네요! Reservationsetter를 두지 않고 필요할 때 추가하는 것이 맞다고 생각합니다!

return reservation;
}

@Override
public Reservation findById(Long id) {
String sql = "SELECT * FROM reservation WHERE id = ?";
try {
return jdbcTemplate.queryForObject(sql, reservationRowMapper, id);
} catch (EmptyResultDataAccessException e) {
return null;
}
}

@Override
public void deleteById(Long id) {
String sql = "DELETE FROM reservation WHERE id = ?";
jdbcTemplate.update(sql, id);
}
}
46 changes: 46 additions & 0 deletions src/main/java/roomescape/repository/ReservationRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package roomescape.repository;

import org.springframework.stereotype.Repository;
import roomescape.domain.Reservation;

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


@Repository
public class ReservationRepository implements ReservationRepositoryImpl{
private final Map<Long, Reservation> reservations = new HashMap<>();
private final AtomicLong atomicLong = new AtomicLong(0);

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

@Override
public Reservation save(Reservation reservation) {
if (reservation == null) {
throw new IllegalArgumentException("예약 정보가 없습니다.");
}

if (reservation.getId() != null && reservations.containsKey(reservation.getId())) {
throw new IllegalArgumentException("이미 등록된 예약입니다.");
}
reservation.setId(atomicLong.incrementAndGet());
reservations.put(reservation.getId(), reservation);

return reservation;
}

@Override
public Reservation findById(Long id) {
return reservations.get(id);
}

@Override
public void deleteById(Long id) {
reservations.remove(id);
}
}
13 changes: 13 additions & 0 deletions src/main/java/roomescape/repository/ReservationRepositoryImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package roomescape.repository;

import roomescape.domain.Reservation;

import java.util.List;

public interface ReservationRepositoryImpl {
Copy link

@mangsuyo mangsuyo May 21, 2024

Choose a reason for hiding this comment

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

저장소가 변경되더라도 service의 코드는 변경되지 않도록 추상화 하신 것 같아요.
인터페이스 이름인 ReservationRepsoitoryImpl에서 Impl은 어떤 걸 의미하나요?

Copy link
Author

Choose a reason for hiding this comment

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

interface에 차이를 주기 위해 implement의 줄임표현인 Impl를 사용했습니다!

Copy link

@mangsuyo mangsuyo May 24, 2024

Choose a reason for hiding this comment

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

인터페이스를 구현하는 클래스에 Impl을 붙이는 일반적인 관행이 있어서 궁금해서 여쭤봤습니다!
InterfaceName + Impl 로 구현클래스 이름을 지으면 어떤 인터페이스를 구현한 클래스인지 알려주는데 더 유리하다고 생각해요 :)


List<Reservation> findAll();
Reservation save(Reservation reservation);
Reservation findById(Long id);
void deleteById(Long id);
}
59 changes: 59 additions & 0 deletions src/main/java/roomescape/service/ReservationService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package roomescape.service;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import roomescape.domain.Reservation;
import roomescape.repository.ReservationJdbcRepository;
import roomescape.repository.ReservationRepository;
import roomescape.dto.ReservationRequest;
import roomescape.dto.ReservationResponse;
import roomescape.exception.BadRequestException;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class ReservationService {

private final ReservationRepository reservationRepository;
private final ReservationJdbcRepository reservationJdbcRepository;
public List<ReservationResponse> findAllReservations() {
List<Reservation> reservations = reservationJdbcRepository.findAll();
List<ReservationResponse> responseDtoList = reservations.stream()
.map(ReservationResponse::from)
.collect(Collectors.toList());

return responseDtoList;
}

public ReservationResponse addReservation(ReservationRequest reservationRequest){
String name = reservationRequest.getName();
LocalDate date = reservationRequest.getDate();
LocalTime time = reservationRequest.getTime();

Reservation reservation = new Reservation();
reservation.setName(name);
reservation.setDate(date);
reservation.setTime(time);

Reservation savedReservation = reservationJdbcRepository.save(reservation);

return ReservationResponse.from(savedReservation);
}

public void cancelReservation(Long id) {
ReservationResponse reservationResponse = findById(id);
reservationJdbcRepository.deleteById(reservationResponse.getId());
}

public ReservationResponse findById(Long id) {
Reservation reservation = reservationJdbcRepository.findById(id);
if(reservation == null) {
throw new BadRequestException("예약을 찾을 수 없습니다. (id=" + id + ")");
}
return ReservationResponse.from(reservation);
}
}
Empty file.
11 changes: 11 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
spring:
h2:
console:
enabled: true
path: /h2-console
datasource:
url: jdbc:h2:mem:database
hikari:
username: sa
password:
driver-class-name: org.h2.Driver
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)
);
Loading