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] 이가연 미션 제출합니다. #380

Open
wants to merge 10 commits into
base: gy102912
Choose a base branch
from
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ 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-devtools'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'
}
Expand Down
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);
}

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

import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
import java.util.NoSuchElementException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.ExceptionHandler;
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.entity.Reservation;
import roomescape.service.ReservationService;

@Controller
public class ReservationController {

private final ReservationService reservationService;

@Autowired

Choose a reason for hiding this comment

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

@Autowired를 붙인것과 안 붙인거는 어떤 차이가 있나요?

ReservationController(final ReservationService reservationService) {
this.reservationService = reservationService;
}


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

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

@GetMapping("/reservations")
@ResponseBody
public List<Reservation> getAllReservations() {
return reservationService.searchAllReservations();
}

@PostMapping("/reservations")
@ResponseBody
public ResponseEntity<Reservation> createReservation(@RequestBody @Valid Reservation reservation) {
Reservation response = reservationService.makeReservation(reservation);
URI location = URI.create("/reservations/" + response.getId());

return ResponseEntity.created(location).body(response);
}

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

return ResponseEntity.noContent().build();
}

@ExceptionHandler({NoSuchElementException.class, MethodArgumentNotValidException.class})

Choose a reason for hiding this comment

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

에러 핸들링을 위해 ExceptionHandler를 추가하셨군요.

이 메서드를 controller에 추가하신 이유는 어떤건가요?

public ResponseEntity<Void> handleException(Exception e) {
return ResponseEntity.badRequest().build();
}
}
59 changes: 59 additions & 0 deletions src/main/java/roomescape/controller/TimeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package roomescape.controller;

import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
import java.util.NoSuchElementException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.ExceptionHandler;
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.entity.Time;
import roomescape.service.TimeService;

@Controller
public class TimeController {

TimeService timeService;

Choose a reason for hiding this comment

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

접근 제어자를 package-private로 두신 이유가 있으실까요??


@Autowired
TimeController(TimeService timeService) {
this.timeService = timeService;
}

@GetMapping("/times")
@ResponseBody
public ResponseEntity<List<Time>> getAllTimes() {
List<Time> body = timeService.searchAllTimes();
return ResponseEntity.ok(body);
}

@PostMapping("/times")
@ResponseBody
public ResponseEntity<Time> createTime(@RequestBody @Valid Time request) {
Time response = timeService.setTime(request);
URI location = URI.create("/times/" + response.getId());

return ResponseEntity.created(location).body(response);
}

@DeleteMapping("/times/{timeId}")
@ResponseBody
public ResponseEntity<Void> deleteTime(@PathVariable Long timeId) {
timeService.removeTime(timeId);

return ResponseEntity.noContent().build();
}

@ExceptionHandler({NoSuchElementException.class, MethodArgumentNotValidException.class})
public ResponseEntity<Void> handleException(Exception e) {
return ResponseEntity.badRequest().build();
}
}
19 changes: 19 additions & 0 deletions src/main/java/roomescape/entity/Reservation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package roomescape.entity;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class Reservation {

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

public static Reservation create(Long id, String name, String date, Time time) {
return new Reservation(id, name, date, time);
}
}
18 changes: 18 additions & 0 deletions src/main/java/roomescape/entity/Time.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package roomescape.entity;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class Time {

private Long id;

private String time;

public static Time create(Long id, String time) {

Choose a reason for hiding this comment

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

정적 팩터리 메서드가 일반적인 생성자와 동일하게 동작하는 것 같아요 별도로 정적 팩터리 메서드를 추가하신 이유가 있으신가요?

return new Time(id, time);
}
}
73 changes: 73 additions & 0 deletions src/main/java/roomescape/repository/ReservationRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package roomescape.repository;

import java.sql.PreparedStatement;
import java.util.List;
import java.util.NoSuchElementException;
import org.springframework.beans.factory.annotation.Autowired;
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.entity.Reservation;
import roomescape.entity.Time;

@Repository
public class ReservationRepository {

@Autowired

Choose a reason for hiding this comment

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

필드 주입으로 jdbcTemplate을 주입해주셨군요.

주입 방법엔 여러가지가 있는데, 필드 주입을 사용하시는 이유가 있으신가요?

JdbcTemplate jdbcTemplate;

private final RowMapper<Reservation> rowMapper = (resultSet, rowNum) ->

Choose a reason for hiding this comment

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

rowMapper를 깔끔하게 분리해주신 게 좋네요!

Reservation.create(
resultSet.getLong("id"),
resultSet.getString("name"),
resultSet.getString("date"),
Time.create(
resultSet.getLong("time_id"),
resultSet.getString("time_value"))
);


public List<Reservation> findAll() {
String selectSql = """
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 jdbcTemplate.query(selectSql, rowMapper);
}

public Reservation create(Reservation reservation) {
KeyHolder keyHolder = new GeneratedKeyHolder();

String insertSql = "INSERT INTO reservation (name, date, time) VALUES (?, ?, ?)";
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(insertSql, new String[]{"id"});
ps.setString(1, reservation.getName());
ps.setString(2, reservation.getDate());
ps.setString(3, String.valueOf(reservation.getTime().getId()));
return ps;
}, keyHolder);

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

public void deleteById(Long id) {
int rowsAffected = jdbcTemplate.update("delete from reservation where id = ?", id);

if (rowsAffected == 0) {
throw new NoSuchElementException();
}
}
}
55 changes: 55 additions & 0 deletions src/main/java/roomescape/repository/TimeRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package roomescape.repository;

import java.sql.PreparedStatement;
import java.util.List;
import java.util.NoSuchElementException;
import org.springframework.beans.factory.annotation.Autowired;
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.entity.Time;

@Repository
public class TimeRepository {

Choose a reason for hiding this comment

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

이름을 Repository라고 지어주셨군요.

Repository와 Dao는 어떤 차이가 있을까요?


private final JdbcTemplate jdbcTemplate;

@Autowired
TimeRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

private final RowMapper<Time> rowMapper = (rs, rowNum) ->
Time.create(rs.getLong("id"), rs.getString("time"));


public List<Time> findAll() {
String selectSql = "select * from time";
return jdbcTemplate.query(selectSql, rowMapper);
}

public Time create(String time) {
KeyHolder keyHolder = new GeneratedKeyHolder();

String insertSql = "INSERT INTO time (time) VALUES (?)";
jdbcTemplate.update((connection) -> {
PreparedStatement ps = connection.prepareStatement(insertSql, new String[]{"id"});
ps.setString(1, time);
return ps;
}, keyHolder);

Long generatedId = keyHolder.getKey().longValue();
return Time.create(generatedId, time);
}

public void deleteById(Long id) {
String deleteSql = "DELETE FROM time WHERE id = ?";

int rowsAffected = jdbcTemplate.update(deleteSql, id);
if (rowsAffected == 0) {
throw new NoSuchElementException();
}
}
}
27 changes: 27 additions & 0 deletions src/main/java/roomescape/service/ReservationService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package roomescape.service;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import roomescape.entity.Reservation;
import roomescape.repository.ReservationRepository;

@Service
public class ReservationService {

@Autowired
private ReservationRepository reservationRepository;


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

public Reservation makeReservation(Reservation reservation) {
return reservationRepository.create(reservation);
}

public void cancelReservation(Long id) {
reservationRepository.deleteById(id);
}
}
31 changes: 31 additions & 0 deletions src/main/java/roomescape/service/TimeService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package roomescape.service;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import roomescape.entity.Time;
import roomescape.repository.TimeRepository;

@Service
public class TimeService {

TimeRepository timeRepository;

@Autowired
TimeService(TimeRepository timeRepository) {
this.timeRepository = timeRepository;
}


public List<Time> searchAllTimes() {
return timeRepository.findAll();
}

public Time setTime(Time time) {
return timeRepository.create(time.getTime());
}

public void removeTime(Long timeId) {
timeRepository.deleteById(timeId);
}
}
5 changes: 5 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.url=jdbc:h2:mem:database;DATABASE_TO_UPPER=false
spring.datasource.username=sa
spring.sql.init.schema-locations=classpath:sql/schema.sql
Loading