-
Notifications
You must be signed in to change notification settings - Fork 78
[Spring Data JPA] 김민지 4-6단계 미션 제출합니다. #190
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
base: mmm307955
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요 민지님, 어느덧 마지막 미션이네요!
JPA를 사용해 보니 어떠신 것 같나요? 코드를 작성하는 데 있어 조금은 더 쉬워진 것 같나요? 아니면 오히려 JPA에 익숙해진다고 더 어려웠나요?
저는 실무에서 JPA를 사용하고 있는데요, 제가 만약 JPA(더 정확히는 Hibernate)를 더이상 사용하지 않는다면, 사용하는 언어가 바뀌어야 할 정도로, 충분히 성숙하고 잘 만들어진 도구(프레임워크)에요.
하지만 어느 도구든 그렇듯, 사용 환경을 편하게 만들어 줄 수는 있어도, 모든 역할을 대신 해줄 수는 없어요.
이러한 점을 잊지 마시고, 도구의 사용법을 익히더라도, 현명하게 도구를 사용하는 법 또한 익히셨으면 좋겠어요.
최근에 이직해서, 다른 곳에 신경 쓸 곳이 많아, 코드 리뷰에 신경을 많이 못 쓴 것 같아 죄송하네요. 😭
요구사항은 모두 만족했으니, 생각할 곳에 대해 리뷰 남겼습니다!
반영하시고, 재요청 부탁드립니다!
#spring.jpa.defer-datasource-initialization=true | ||
spring.jpa.show-sql=true | ||
spring.jpa.properties.hibernate.format_sql=true | ||
spring.jpa.ddl-auto=create-drop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JPA를 처음 사용했을 때, 이 설정으로 인해 치명적인 사고가 발생할 수 있어요.
해당 설정은 어떤 것이 있고, 사고를 방지하려면 어떻게 할 수 있을까요?
private MemberDao memberDao; | ||
private final MemberRepository memberRepository; | ||
|
||
public MemberService(MemberDao memberDao) { | ||
this.memberDao = memberDao; | ||
public MemberService(MemberRepository memberRepository) { | ||
this.memberRepository = memberRepository; | ||
} | ||
|
||
public MemberResponse createMember(MemberRequest memberRequest) { | ||
Member member = memberDao.save(new Member(memberRequest.getName(), memberRequest.getEmail(), | ||
memberRequest.getPassword(), "USER")); | ||
Member member = memberRepository.save( | ||
new Member(memberRequest.getName(), memberRequest.getEmail(), | ||
memberRequest.getPassword(), "USER")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
데이터 접근 계층에 대한 변경이 있었을 뿐인데, 이와 연관 없는 비즈니스 계층까지 변경이 발생했네요.
(미션 요구사항을 따라했다면 당연히 발생한 문제이니, 본인의 실수나 잘못이 아니에요)
코드 변경이 많아질 수록, 실수로 인해 의도치 않은 버그의 발생 확률이 높아지고, 연관이 없는 코드의 변경이 많아질 수록, 명확한 변경 사항의 추적이 힘들어질 거에요.
또한 JPA의 학습 곡선이 너무 높거나, 내부 기술 제약으로 인해 다시 JdbcTemplate을 사용한 코드로 되돌려야 한다면, 다시 이러한 변경이 발생할 것 같아요.
그렇다면 데이터 접근 계층의 구조를 어떻게 설계하고 사용했어야 이러한 연관 없는 변경을 막을 수 있을까요?
@Entity | ||
public class Member { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GeneratedValue
의 strategy
속성을 지정하지 않는다면 기본 값은 어떤게 사용되나요?
또한 strategy의 종류는 어떤게 있는지 알아봐도 좋을 것 같아요.
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "member_id") | ||
private Member member; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reservation
에서 Member
필드는 어떤 로직(또는 기술적 제약) 때문에 필요한가요?
Member
는 어떤 목적을 위해 존재하며, Member
가 가지는 행위(메서드)는 어떤게 있나요?
그렇다면 Reservation
에서 Member
를 필드로 가지고 있으며, 노출(getter)시키는 것은 어떤 장/단점이 있을 것 같나요?
public interface WaitingRepository extends JpaRepository<Waiting, Long> { | ||
@Query("SELECT new roomescape.waiting.WaitingWithRank(" + | ||
" w, " + | ||
" (SELECT COUNT(w2) " + | ||
" FROM Waiting w2 " + | ||
" WHERE w2.theme = w.theme " + | ||
" AND w2.date = w.date " + | ||
" AND w2.time = w.time " + | ||
" AND w2.id < w.id)) " + | ||
"FROM Waiting w " + | ||
"WHERE w.member.id = :memberId") | ||
List<WaitingWithRank> findWaitingsWithRankByMemberId(Long memberId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JPA를 사용해보니 기존 JdbcTemplate을 사용하는 것에 비해 어떤것 같나요?
어떤 문제의 경우, JPA 보다는 JdbcTemplate를 직접 사용하는 것이 더 나은 해결책일 수 있을 것 같다는 생각이 들지는 않으셨나요?
org.springframework.stereotype.Repository
어노테이션의 Javadoc을 보면 다음과 같이 적혀있어요.
Indicates that an annotated class is a "Repository", originally defined by Domain-Driven Design (Evans, 2003) as "a mechanism for encapsulating storage, retrieval, and search behavior which emulates a collection of objects".
Javadoc에서도 나와있듯, Repository
는 유명한 DDD라는 개발 방법론에서 나온 용어인데요, 이를 GPT에게 물어보니 다음과 같은 답변을 해줬어요.
### Repository의 정의 (아키텍처/DDD 관점)
- Repository는 도메인 모델에서 엔티티 집합에 접근하기 위한 추상화 계층입니다.
- 쉽게 말해, DB 같은 저장소에 직접 접근하지 않고, 마치 in-memory 컬렉션을 다루듯 엔티티를 가져오고 저장하게 해주는 인터페이스 역할을 합니다.
- Eric Evans의 DDD 책(“Domain-Driven Design”)에서 정의된 패턴으로, Domain Layer와 Infrastructure Layer 사이의 경계(anti-corruption layer) 역할을 합니다.
### 핵심 특징
1. 컬렉션처럼 보이게 한다
- Repository는 add, remove, findById, findByCondition 같은 메서드를 제공해서, DB 조회/저장 작업을 컬렉션 조작처럼 느끼게 합니다.
2. 저장소의 세부 구현을 숨긴다
- 비즈니스 로직은 DB, 캐시, 외부 API, 파일 시스템이 무엇인지 몰라도 됩니다.
- 구체적인 저장 방식(JPA, JDBC, MongoDB 등)은 Infrastructure Layer에서 담당합니다.
3. 도메인 친화적인 인터페이스 제공
- 도메인에 의미 있는 질의(query) 메서드를 노출합니다. 예: findProductsByCategory(Category category)
- SQL 쿼리나 스키마 용어 대신, 도메인 모델 언어(Ubiquitous Language)에 맞게 설계합니다.
여기서 WaitingRepository
는 Repository 패턴을 잘 지킨 것 같아요. (인터페이스, 메서드 네이밍)
만약, JdbcTemplate 사용이 반드시 필요한 곳이 생긴다면, 어떻게 구현할 수 있을까요?
WaitingRepository
를 인터페이스로 설계해서 이러한 기술적 요구사항을 큰 변경 없이 반영할 수 있을 것 같은데, 정말 그럴까요?
오히려 JPA를 사용함으로 인해, 특정 기술에 Lock in되어 변경에 자유롭지 못하게 된 것 같지 않나요?
result.addAll(reservations.stream() | ||
.map(MyReservationResponse::from) | ||
.toList()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MyReservationResponse::from
의 메서드는 다음과 같이 구현되어 있군요.
public static MyReservationResponse from(Reservation reservation) {
return new MyReservationResponse(
reservation.getId(),
reservation.getTheme().getName(),
reservation.getDate(),
reservation.getTime().getTime(),
"예약"
);
}
만약 저장된 Reservation
의 총 개수가 10개라면, reservationRepository.findByMemberId(memberId)
메서드로 발생하는 쿼리를 포함하여, 총 몇 개의 쿼리가 발생할까요?
그리고 이러한 문제를 무엇이라고 할까요?
|
||
public ReservationResponse save(ReservationRequest request, LoginMember member) { | ||
String name = request.getName(); | ||
@Transactional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JPA를 사용할 떄, @Transactional
을 사용하지 않으면 어떤 영향이 있을까요?
안녕하세요 글렌!
벌써 배포 전 미션인 JPA 단계까지 왔네요!
이번에도 잘 부탁드립니다!
변경사항
schema.sql
파일에 있는 create문들을 삭제했습니다.application.properties
에서spring.jpa.ddl-auto=create-drop
로 되어있어 JPA가 엔티티를 기반으로 테이블을 자동 생성하도록 하는데,schema.sql
파일에서도 같은 테이블을 생성하려고 해서 충돌이 발생하여schema.sql
파일을 수정하였습니다.MySQL의 예약어 충돌을 피하기 위해
@Column(name = "time_value")
사용했습니다.private String time;
을 보고 time이라는 컬럼을 만들려고 하는데, time은 sql 예약어라서 컬럼명으로 사용하면 구문 에러가 발생할 수 있기 때문에 다른 컬럼명을 사용하기 위해@Column
어노테이션을 사용해서 다른 컬럼명을 사용했습니다.@ManyToOne
와 같이 여러 개의 예약은 하나의 시간대를 참조할 수 있기 때문에
@ManyToOne
을 사용하였습니다. (theme도 마찬가지입니다!)@JoinColumn
로 외래키 컬럼명을 지정하였습니다.waiting.member_id
로 접근하지만 JPQL은w.member.id
와 같이.
으로 접근할 수 있다는 것을 알게 되었습니다. 따라서 그에 맞게WaitingRepository
의 JPQL을 수정하였습니다.