Skip to content

Conversation

@mintcoke123
Copy link

@mintcoke123 mintcoke123 commented Nov 20, 2025

안녕하세요! 정상희 리뷰어님!
스프링 미션 5,6,7 제출하겠습니다~!
이번 미션도 잘부탁드립니다!

🚀 미션 내용

이번 5,6,7단계 미션은 db와의 연결과 상호작용을 다루는 내용이었습니다.

📊 구현 순서

▶5단계

  1. build.gradle의 의존성으로 JDBC, H2 추가
  2. schema.sql: reservation 테이블 생성

▶6단계

  1. 조회 로직 JdbcTemplate을 사용하게끔 전환
  2. ReservationList에 JdbcTemplate 주입, RowMapper 작성
  3. API 연동

▶7단계

  1. 생성/삭제 DB 반영
  2. create(..): KeyHolder로 id 획득 → 201, Location: /reservations/{id}
    delete(): DELETE ... 실행; 영향 0이면 NotFoundReservationException(404)
  3. dao 레이어 추가 후 구조 변경

mintcoke123 and others added 30 commits November 6, 2025 17:49
Copy link

@SANGHEEJEONG SANGHEEJEONG left a comment

Choose a reason for hiding this comment

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

안녕하세요, 동현님 오랜만입니다!
이번 미션도 파이팅 해봐요 🏃‍♂️🙌

import java.util.List;

@Repository
public class ReservationDao {

Choose a reason for hiding this comment

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

동현님이 생각하시는 DAO와 Repository의 차이는 무엇인가요?
또한 DAO로 이름 지으신 후 @Repository 어노테이션을 사용하셨는데 이유가 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

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

dao는 service레이어 이전에 데이터베이스와 직접 상호작용하는 계층으로, 순수한 데이터 접근 로직을 담당합니다.
Repository는 도메인 컬렉션처럼 동작하는 추상화라고 하는군요! 처음 알았습니다.
dao의 어노테이션을 Repository라고 지은 이유는, dao 계층을 표현할 때에는 @Repository 어노테이션을 사용한다는 레퍼런스를 참고했기 때문입니다.

https://whitekeyboard.tistory.com/178

찾아보니 @Repository 어노테이션은 JDBC 에서 발생하는 SQLException 같은 걸 스프링이 DataAccessException 계열 예외로 변환해 주는 역할을 수행한다고 하네요

this.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.

👍

return ps;
}, keyHolder);
return keyHolder.getKey().longValue();
}

Choose a reason for hiding this comment

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

현재 insert 메서드에서 KeyHolder와 JDBC 템플릿을 사용하여 데이터 삽입 후 자동 생성된 id를 가져오는 구현 방식을 잘 구현해주신 것 같아요 👍

Simplejdbcinsert라는 객체를 들어보셨을까요? 참고 사항으로 말씀드리자면, 해당 객체를 사용한다면 코드가 좀 더 간결해진다는 장점이 있습니다.

Copy link
Author

Choose a reason for hiding this comment

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

저번 스터디에서 진행했습니다! 코드 길이도 짧아지고 가시성도 좋아지네요. 적용 완료했습니다!

import java.util.HashMap;
import java.util.concurrent.atomic.AtomicLong;

@Component

Choose a reason for hiding this comment

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

@Service가 아닌 @Component를 붙인 이유가 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

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

레이어는 @component어노테이션으로 싱글톤으로 만들어야 하기 때문에, @component를 사용하였고 레이어마다 어노테이션이 다른 것을 중간에 알았지만 수정을 못했습니다. 수정했습니다!

return toResponse(reservation);
long id = reservationDao.insert(request.name(), request.date(), request.time());
return new ReservationResponse(id, request.name(), request.date(), request.time());
}
Copy link

@SANGHEEJEONG SANGHEEJEONG Nov 22, 2025

Choose a reason for hiding this comment

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

1️⃣ 동현님이 응답 DTO를 사용하시는 이유가 무엇인지 궁금합니다!

2️⃣ 이런 식으로 조립하여 사용하고 계신 것 같은데요,

a. findAll 에서는 Entity -> Response DTO
b. create 에서는 (id를 제외한 나머지 필드) Request DTO -> Response DTO

b번과 같은 경우 요청 DTO에서 응답 DTO를 바로 조립하고 있습니다.
만약 DB에 기본값을 설정하는 경우가 생길 때, 이 방식에는 어떤 문제가 발생할 수 있을까요?

아래는 위의 문제를 해결하기 위한 방법의 힌트입니다.
Image

Copy link
Author

@mintcoke123 mintcoke123 Nov 25, 2025

Choose a reason for hiding this comment

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

  1. 응답 dto를 사용한 이유는, dto로 도메인 모델과 API 응답 모델을 분리해 변경에 대한 영향도를 줄이는 것이 이후 리팩토링에 더욱 효과적이라고 생각했기 때문입니다.
  2. 지금 코드에서는 말씀주신대로 dto 생성 시 서비스에서 request 값으로 바로 Response를 만들고 있었기 때문에 db의 기본값이 달라지면(ex: string 트리밍 등) 실제 저장된 값과 응답이 달라질 수 있다는 것을 알게 되었습니다!

저장 직후 DB에서 방금 저장한 레코드를 다시 조회해 그 값으로 응답을 생성하도록 로직을 변경하였습니다!
수정했습니다!

Comment on lines 36 to 41
throw new InvalidReservationRequestException(
"잘못된 예약 요청입니다. " +
"name=" + request.name() +
", date=" + request.date() +
", time=" + request.time()
"name=" + request.name() +
", date=" + request.date() +
", time=" + request.time()
);

Choose a reason for hiding this comment

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

지금 코드에서는 예외 메시지에 name, date, time 같은 요청값을 그대로 넣고 있는데요. 개발 환경에서는 큰 문제가 없지만 운영 환경에서는 사용자 입력값이 그대로 노출되는 문제가 발생할 수 있어서 보통 로그에는 실제 요청값을 남기고, 사용자에게는 간단한 메시지만 전달하는 방식을 사용하는 편입니다!

<로그>

 logger.warning("Invalid request: " + request)

<예외 메시지>

 throw new InvalidReservationRequestException("잘못된 예약 요청입니다.")

Copy link
Author

Choose a reason for hiding this comment

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

logger를 사용하는 게 캡슐화에도 더 좋아보이네요!
해당 방식으로 수정했습니다!

Copy link

@SANGHEEJEONG SANGHEEJEONG left a comment

Choose a reason for hiding this comment

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

리뷰 잘 반영해주셨네요!! 짱 🙌
다음 미션도 진행하셔야 하니, 이정도로 마무리해도 좋을 것 같습니다.
수고하셨습니다 ~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants