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] 원태연, 이승용 미션 제출합니다. #259

Open
wants to merge 22 commits into
base: kokodak
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ 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'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'
}
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/roomescape/common/presentation/IndexController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package roomescape.common.presentation;

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

@Controller
public class IndexController {

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

@GetMapping("/time")
public String time() {
return "time";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package roomescape.reservation.application;

import java.util.List;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import roomescape.reservation.domain.Reservation;
import roomescape.reservation.domain.ReservationRepository;
import roomescape.reservation.presentation.dto.SaveReservationRequest;
import roomescape.time.domain.Time;
import roomescape.time.domain.TimeRepository;

@Service
public class ReservationService {

private final ReservationRepository reservationRepository;
private final TimeRepository timeRepository;

public ReservationService(@Qualifier("jdbcReservationRepository") final ReservationRepository reservationRepository,
final TimeRepository timeRepository) {
this.reservationRepository = reservationRepository;
this.timeRepository = timeRepository;
}

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

public Reservation save(final SaveReservationRequest request) {
final Time time = timeRepository.findById(request.time());
final Reservation reservation = new Reservation(request.name(), request.date(), time);
return reservationRepository.save(reservation);
}

public void deleteById(final Long id) {
reservationRepository.deleteById(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package roomescape.reservation.domain;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Repository;
import roomescape.reservation.domain.exception.NotExistReservationException;

@Repository
public class InMemoryReservationRepository implements ReservationRepository {

private final Map<Long, Reservation> reservations = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(0);

@Override
public List<Reservation> findAll() {
return List.copyOf(reservations.values());
}

@Override
public Reservation save(Reservation reservation) {
if (reservation.id() == null) {
reservation.setId(idGenerator.incrementAndGet());
}
reservations.put(reservation.id(), reservation);
return reservation;
}

@Override
public void deleteById(Long id) {
if (!reservations.containsKey(id)) {
throw new NotExistReservationException("해당 예약을 찾을 수 없습니다.");
}
reservations.remove(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package roomescape.reservation.domain;

import java.sql.PreparedStatement;
import java.util.List;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository;
import roomescape.reservation.domain.exception.NotExistReservationException;
import roomescape.time.domain.Time;

@Repository
public class JdbcReservationRepository implements ReservationRepository {

private final JdbcTemplate jdbcTemplate;

public JdbcReservationRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

@Override
public List<Reservation> findAll() {
String sql = "SELECT \n"
+ " r.id as reservation_id, \n"
+ " r.name, \n"
+ " r.date, \n"
+ " t.id as time_id, \n"
+ " t.time as time_value \n"
+ "FROM reservation as r inner join time as t on r.time_id = t.id";

return jdbcTemplate.query(sql, (rs, rowNum) -> new Reservation(
rs.getLong("id"),
rs.getString("name"),
rs.getString("date"),
new Time(rs.getLong("time_id"), rs.getString("time_value"))
));
}

@Override
public Reservation save(Reservation reservation) {
final String sql = "INSERT INTO reservation (name, date, time_id) VALUES (?, ?, ?)";

final GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
final PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"});
ps.setString(1, reservation.name());
ps.setObject(2, reservation.date());
ps.setLong(3, reservation.time().id());
return ps;
}, keyHolder);

final long generatedKey = keyHolder.getKey().longValue();
return new Reservation(generatedKey, reservation.name(), reservation.date(), reservation.time());
}

@Override
public void deleteById(final Long id) {
String sql = "DELETE FROM reservation WHERE id = ?";
int updatedRowCount = jdbcTemplate.update(sql, id);
if (updatedRowCount == 0) {
throw new NotExistReservationException("해당 예약을 찾을 수 없습니다.");
}
}
}
43 changes: 43 additions & 0 deletions src/main/java/roomescape/reservation/domain/Reservation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package roomescape.reservation.domain;

import roomescape.time.domain.Time;

public class Reservation {

private Long id;
private final String name;
private final String date;
private final 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 Reservation(String name, String date, Time time) {
this(null, name, date, time);
}

void setId(Long id) {
this.id = id;
}

public Long id() {
return id;
}

public String name() {
return name;
}

public String date() {
return date;
}

public Time time() {
return time;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package roomescape.reservation.domain;

import java.util.List;

public interface ReservationRepository {

List<Reservation> findAll();

Reservation save(Reservation reservation);

void deleteById(Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package roomescape.reservation.domain.exception;

public class NotExistReservationException extends RuntimeException {

public NotExistReservationException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package roomescape.reservation.presentation;

import java.net.URI;
import java.util.List;
import org.springframework.http.ResponseEntity;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import roomescape.reservation.application.ReservationService;
import roomescape.reservation.domain.Reservation;
import roomescape.reservation.presentation.dto.ReservationResponse;
import roomescape.reservation.presentation.dto.SaveReservationRequest;

@RequestMapping("/reservations")
@RestController
public class ReservationController {

private final ReservationService reservationService;

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

@GetMapping
public ResponseEntity<List<ReservationResponse>> reservations() {
final List<Reservation> reservations = reservationService.findAll();
final List<ReservationResponse> response = reservations.stream()
.map(ReservationResponse::from)
.toList();
return ResponseEntity.ok(response);
}

@PostMapping
public ResponseEntity<ReservationResponse> save(@RequestBody SaveReservationRequest request) {
final Reservation reservation = reservationService.save(request);
final ReservationResponse response = ReservationResponse.from(reservation);
final URI location = URI.create("/reservations/" + response.id());
return ResponseEntity.created(location).body(response);
}

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

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import roomescape.reservation.domain.exception.NotExistReservationException;
import roomescape.reservation.presentation.exception.BadReservationSaveException;

@ControllerAdvice
public class ReservationExceptionHandler {

@ExceptionHandler(BadReservationSaveException.class)
public ResponseEntity<String> handleBadReservationSaveException(BadReservationSaveException exception) {
return ResponseEntity.badRequest()
.body(exception.getMessage());
}

@ExceptionHandler(NotExistReservationException.class)
public ResponseEntity<String> handleNotExistReservationException(NotExistReservationException exception) {
return ResponseEntity.badRequest()
.body(exception.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package roomescape.reservation.presentation.dto;

import roomescape.reservation.domain.Reservation;

public record ReservationResponse(
Long id,
String name,
String date,
String time
) {

public static ReservationResponse from(Reservation reservation) {
return new ReservationResponse(
reservation.id(),
reservation.name(),
reservation.date(),
reservation.time().time()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package roomescape.reservation.presentation.dto;

import java.util.Arrays;
import roomescape.reservation.presentation.exception.BadReservationSaveException;

public record SaveReservationRequest(
String name,
String date,
Long time
) {

public SaveReservationRequest {
validate(name, date, time);
}

private void validate(String name, String date, Long time) {
validateBlanks(name, date);
}

private void validateBlanks(String... fields) {
boolean isBlankFieldExist = Arrays.stream(fields)
.anyMatch(String::isBlank);
if (isBlankFieldExist) {
throw new BadReservationSaveException("빈 값이 입력되었습니다.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package roomescape.reservation.presentation.exception;

public class BadReservationSaveException extends RuntimeException {

public BadReservationSaveException(String message) {
super(message);
}
}
Loading