-
Notifications
You must be signed in to change notification settings - Fork 77
[Spring Data JPA] 신혜빈 미션 제출합니다. #108
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: shin378378
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.
안녕하세요 혜빈님 ! 저도 원래 계시던 메인테이너와 리뷰어분들이 혜빈님 짱 멋있다는 이야기를 많이 해주셨어요. :D
과제도 바쁘셨을텐데 어려운 미션 하시느라 고생 많으셨어요!
클래스 위치에 대해
저도 클래스의 위치를 정할 때 Interceptor, Argument Resolver처럼
특정 도메인에 속하지 않고 전역적으로 적용되는 클래스들의 위치를 정하는 게 항상 어렵습니다.
정해진 정답도 없는 것 같구요!
이번 미션에서는 도메인 단위로 패키지가 분리되어 있으니
인증/인가와 관련된 클래스들을 auth 패키지에 둔 지금 구조가 좋은 것 같아요. :D
그런데 CookieUtil은 현재 인증에 필요한 token을 추출하는 메서드만 있어 auth 패키지 외에 다른 패키지에서 쓸 일이 없고, 같은 이유로 다른 패키지에서 사용해서도 안 되기 때문에
util 패키지에 분리되어 있는 것보다 auth 패키지에 소속되어 있는 것이 더 자연스럽지 않을까라는 생각이 들어요. 혜빈님의 의견은 어떠신지 궁금해요!
AdminInterceptor
클래스는 member라는 파일에 들어가야하는 클래스일까, 아님 util이라는 파일에 들어가야 하는 클래스일까
'유틸성 클래스' 라는 키워드를 통해서 먼저 유틸의 정의를 알아보셨음 좋겠어요! 그리고 AdminInterceptor는 유틸성 클래스의 조건을 만족하는 지 고민해 보신다면, 위 의문은 자연스럽게 해결되리라 생각해요.
어노테이션
어떤 점이 부족한지 먼저 알려주셔서 감사해요 혜빈님! :D
스프링, 그 중에서도 JPA는 다양한 기능을 제공하는 데에 어노테이션을 정말 적극적으로 활용하는 프레임워크에요. 그래서 이번 리뷰는 JPA에서 제공하는 다양한 어노테이션들을 알아보고 사용해보실 수 있는 방향으로 남겨보았어요.
Repository 의존성
현재 혜빈님의 도메인 구조는 Reservation, Theme, Time, Member, Waiting이라는 도메인이
모두 동일한 레벨의 독립된 도메인으로 취급되고 있습니다.
객체들이 필요에 따라 서로 연관관계를 맺기는 하지만, Time이 Reservation에 포함된다라는 식의 포함관계를 맺고있지는 않다는 뜻인데요,
이럴 경우에는 각 도메인 객체를 조회하기 위해서 각각의 Repository를 사용해야 하니,
혜빈님의 서비스가 Repository를 많이 들게 되는 것은 조금 못생겼으나 최선인 것으로 보입니다.
만약 Repository의 수를 줄이고 싶으시다면
엔티티들 사이의 관계를 잘 분석한 후, 연관관계보다 포함관계가 더 어울리는 엔티티들을 합치는 방법이 있습니다.
예를 들어 Time이 Reservation과 독립된 개념이 아니라 Reservation에 포함된 아이라는 판단을 하셨다면, Time 객체를 별도의 테이블로 분리하지 않고 Reservation의 컬럼으로 병합하는 것도 그 예시 중 하나가 될 수 있죠!
하지만 혜빈님의 앞선 미션에서 은우님과 나누셨던 대화처럼, 서로 다른 도메인을 별도 테이블(엔티티)로 분리하는 것과 병합하는 것 둘은 서로 상반된 장단점을 가지고 있습니다. 그러니 본인만의 기준에 맞게 고민해보시면 좋을 것 같습니다.
src/main/resources/data.sql
Outdated
(1, '어드민', '2024-03-01', 3, 3); | ||
|
||
INSERT INTO reservation (name, date, time_id, theme_id) | ||
VALUES ('브라운', '2024-03-01', 1, 2); |
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.
이 SQL문을 보시면 브라운의 예약정보를 저장할 때 member_id가 없습니다.
예약 데이터인데 누가 예약을 했는지에 대한 정보가 없다면 불완전한 데이터라고 할 수 있어요.
하지만 어떤 이유로든 이런 식의 잘못된 데이터를 저장할 가능성이 있고,
잘못된 데이터가 저장되는 것을 예방하려면 테이블을 생성할 때 적절한 제약사항을 명시해야 합니다.
JPA를 사용하는 환경에서는 엔티티 클래스를 바탕으로 DDL문을 만들어주는데,
현재 reservation 테이블을 생성하는 DDL문은 아래와 같습니다.
(애플리케이션을 실행할 때 콘솔에 출력되는 로그를 통해 볼 수 있습니다 !)
create table reservation (
id bigint generated by default as identity,
member_id bigint,
theme_id bigint,
time_id bigint,
date varchar(255),
name varchar(255),
primary key (id)
)
데이터를 저장할 때 필요한 정보가 누락되지 않게 하려면
컬럼을 선언할 때 not null 제약사항을 명시해주면 돼요. 이렇게요 !
create table reservation (
id bigint generated by default as identity,
member_id bigint not null,
theme_id bigint not null,
time_id bigint not null,
date varchar(255) not null,
name varchar(255) not null,
primary key (id)
)
JPA에서 테이블을 생성할 때 이 not null 제약사항을 포함하게 하려면
Reservation 엔티티를 어떻게 수정해야 할까요?
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테이블에 null값이 포함되지 않게 하기 위해서는 아래의 2가지 방법이 존재한다고 합니다.
2개를 함께 사용하기도 한다고 하는 데요.
@NotNull
애플리케이션 레벨에서 엔티티 저장 전 유효성을 검증에 null 값이 있으면 예외 발생@Column(nullable = false)
데이터베이스 레벨에서 테이블 생성 시 NOT NULL 조건을 추가해 null값 자체를 차단
<질문>
@NotNull
를 통해서 1차적으로 애플리케이션 레벨에서의 null값을 막아주었음에도 @Column(nullable = false)
를 사용하주는 이유가 무엇인가요? 잘 있던 데이터가 테이블을 작성할 때 없어지기도 하나요? 그렇다면 이유는 무엇인가요??
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.
관련 설정을 변경하지 않았다면, @NotNull과 @column(nullable = false)을 함께 사용할 필요가 없습니다.
엔티티의 필드에 javax.validation에서 제공하는 @NotNull을 사용하면
하이버네이트에서 DDL을 작성할 때 not null 제약조건을 붙여주거든요!
@column(nullable = false)의 역할까지 @NotNull이 같이 해준다는 뜻입니다.
member_id bigint not null
하이버네이트에서는 @column(nullable = false)보다 @NotNull을 사용하는 것을 더 선호하는데요.
왜냐하면 @column(nullable = false)만을 사용하여 만든 엔티티를 데이터베이스에 저장하려 하면
일단 데이터베이스에 insert문을 쏘고, 데이터베이스에서 뱉은 SQLException을 터뜨립니다.
반면 @NotNull을 사용한 엔티티는 객체를 생성하여 영속화를 시도할 때
데이터베이스가 아닌 애플리케이션 레벨에서 null 여부를 확인하여 예외를 터뜨립니다.
불필요한 데이터베이스 연결로 리소스를 낭비하지 하지 않아도 되는 것이죠!
두 어노테이션의 차이를 비교한 블로그와 밸덩 포스트가 있으니 참고해보시면 좋습니다.
https://dd-developer.tistory.com/116
http://baeldung.com/hibernate-notnull-vs-nullable
private Long id; | ||
|
||
private String name; | ||
private String email; | ||
private String password; | ||
private String role; |
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.
Member의 role에 들어가는 값으로는 'ADMIN', 'USER' 이렇게 두 가지가 있습니다.
이 두 값은 비즈니스에 뭔가 대대적인 개편이 있지 않으면 잘 변하지 않는, 상수와 같은 아이들이에요!
자바에서는 이러한 상수들의 집합을 enum 객체를 사용하여 편하게 관리할 수 있는데요,
이 role 필드를 enum으로 관리할 수 있다면 훨씬 관리하기 편할 것 같습니다.
JPA의 엔티티에서 필드의 타입으로 enum을 사용하려면 어떻게 해야 하고, 또 그 과정에서 어떤 것을 고려해야 할까요?
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의 엔티티에서 필드의 타입으로 enum을 사용하려면?>
- enum을 만들기
- enum을 사용하는 필드에
@Enumerated
의 옵션을 명시해준다. (enum 값을 데이터베이스에 어떻게 저장할지 정하기 위함)
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.
<고려사항>
- enum의 저장형식 고려하기
EnumType.STRING
: enum 값의 이름을 데이터베이스에 저장EnumType.ORDINAL
: enum 값의 순서(index)를 데이터베이스에 저장
EnumType.STRING
을 사용하면 enum의 이름을 활용하기 때문에 순서가 바뀌어도 별 문제가 없지만
EnumType.ORDINAL
은 enum의 순서가 바뀔 경우 데이터 무결성이 깨질 가능성이 있음
-> EnumType.STRING
타입을 사용하는 것이 좋음
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.
- enum에 값이 추가될 가능성을 염두해두고 코드짜기
@JsonProperty
를 사용하여 직렬화/역직렬화 시의 값을 지정할 지, 말지 고려하기
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.
<질문>
-
리뷰어님께서 이 질문을 하신 이유가 제가 위에 적어둔 사항들을 고려하길 바라는 마음에 질문하신 게 맞나요?
혹시 제가 생각하지 못한, 리뷰어님께서 제가 추가로 고찰하길 바라시는 다른 사항이 있는 지 궁금합니다! -
제가 role이 들어가는 다른 부분들은 다 아래와 같이 enum형태로 고쳤는 데
if (!Role.ADMIN.equals(member.getRole())) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "사용자를 찾을 수 없습니다.");
}
유일하게 쿼리부분만 enum으로 고치지 못했습니다 ㅜㅠ
쿼리에는 enum 활용을 못 하나요?? 다른 활용방안이 있는 지 궁금합니다!!
@Override
@Transactional
public void run(ApplicationArguments args) throws Exception {
entityManager.createNativeQuery(
"INSERT INTO member (name, email, password, role) " +
"VALUES ('어드민', 'admin@email.com', 'password', 'ADMIN'),\n" +
"('브라운', 'brown@email.com', 'password', 'USER')"
).executeUpdate();
}
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.
-
맞습니다! 짱 잘해주셨습니다.
추가로, 애플리케이션에서는 ENUM을 사용하지만 데이터베이스에는 다른 형태로 들어가길 원할 수도 있습니다.
(Role.MEMBER를 데이터베이스에 넣을 땐 소문자로 member 이렇게 넣고 싶다던가)
이럴 땐 데이터베이스 <-> Java 코드 사이의 값 변환을 도와주는 attributeconverter 라는 것이 있습니다.
실무에서 프로젝트할 때 굉장히 많이 사용합니다. 한 번 학습해보시면 좋을 것 같아요! -
거기까지 고치실 필요 없습니다!
꼭 하고 싶으시다면, String 클래스의 formatted() 메서드를 활용해서 ADMIN, USER에 들어갈 값을 동적으로 정의해 보세요.
private Long id; | ||
|
||
@Column(name = "time_value") | ||
private String value; |
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.
자바에서는 '시간'을 관리하는 객체로 LocalDateTime, LocalDate, LocalTime 등의 클래스를 제공합니다.
문자열 대신 위 객체를 사용하면 시간과 관련해서 편리한 기능을 많이 사용할 수 있어 훨씬 좋은데요! :)
아래 두 유의사항에 신경쓰면서 ,
각 엔티티에서 시간을 나타내는 필드의 타입을 시간 관리 객체로 바꾸어주세요.
- http requestBody 시간 표시방법("2024-03-01", "10:00")이 변경되지 않게 주의해주세요!
- 데이터베이스에 저장되는 시간 표시방법("2024-03-01", "10:00")이 변경되지 않게 주의해주세요!
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.
지금 미션에서는 시간을 따로 처리할 일이 없지만,
시간 관련 기능이 좀 더 생긴다면 리뷰어님 말씀대로 LocalTime
형태로 관리하는 것이 편리할 것이라고 판단되네요!!
LocalTIme
을 사용하도록 한 번 리팩토링 해봤습니다. 실제로 작성해보는 것만큼 이해가 빠른 방법은 없으니까요 ㅎㅎ
아래와 같이 LocalTime형식으로 시간과 관련한 기능을 처리하고
또 String형식으로 화면에 값을 띄울 수 있게 처리하였습니다!!
@Column(name = "time_value")
private LocalTime value;
public LocalTime getValue() {
return value;
}
public String getValueByString() {
return value.toString();
}
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
private String date; |
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.
ditto, 시간 관리 객체로 바꾸어보세요! ;)
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.
시간 관리 객체 역시 LocalTIme을 이용해 바꾸어 주었습니다.
지금은 예약을 시간순대로 정렬하는 등, 시간을 활용한 계산이 필요없지만 행여 나중에 생긴다면
LocalTime으로 시간을 관리하는 것이 훨씬 편리할 거 같아요!!
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.
앞으로 LocalTime기능을 잘 사용해보겠습니다:) 감사합니다:)
final List<Reservation> reservations = reservationRepository.findByMemberId(member.getId()); | ||
final List<WaitingWithRank> waitingWithRanks = waitingRepository.findWaitingsWithRankByMemberId(member.getId()); | ||
return MyReservationResponse.of(reservations, waitingWithRanks); | ||
} |
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.
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.
여기서 발생한 현상은 N+1문제입니다!
<N+1 문제>
한 번의 쿼리로 해결할 수 있는 작업이 N개의 추가 쿼리를 생성하면서 비효율적으로 처리되는 상황이다.
<동작방식>
- 기본 쿼리 (1번 실행)
처음에는 특정 엔티티(테이블)의 데이터를 조회하기 위해 한 번의 SQL 쿼리를 실행 - 추가 쿼리 (N번 실행)
기본 쿼리를 통해 로드된 데이터와 연관된 데이터를 가져오기 위해, 각각 별도의 쿼리가 실행
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.
<문제가 발생한 이유>
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "time_id")
private Time time;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "theme_id")
private Theme theme;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
Reservation
엔티티에 있는 Theme
과 Time
이 @ManyToOne(fetch = FetchType.LAZY)
로 선언되어 있기 때문이다.
ex) FetchType.LAZY
란?
데이터베이스에서 해당 필드에 접근할 때마다 데이터베이스에서 추가 쿼리를 실행해 데이터를 가져오는 것
-> 따라서, Reservation
리스트를 가져오고 나서 각각의 Theme
나 Time
에 접근할 때마다 추가적인 SQL 쿼리가 실행된다.
-> 이것이 N+1 문제
의 원인이다.
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.
<문제해결방법>
아래와 같이
ReservationRepository에 있는 findByMemberId
메소드에 @EntityGraph(attributePaths = {"theme", "time", "member"})
어노테이션을 붙여주어 명시적으로 연관 관계를 함께 fetch해주었습니다.
@EntityGraph(attributePaths = {"theme", "time", "member"})
List<Reservation> findByMemberId(Long memberId);
@EntityGraph(attributePaths = {"theme", "time", "member"})
어노테이션을 붙이기 전 :
쿼리 호출 횟수는 11번@EntityGraph(attributePaths = {"theme", "time", "member"})
어노테이션을 붙인 후 :
쿼리 호출 횟수는 5번
위의 어노테이션을 붙인 후 쿼리호출 횟수가 눈에 띄게 줄어 N+1문제는 조금 없앴습니다!!
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.
<질문 1>
저도 여우님과 마찬가지로 repository를 3번 사용하니 SQL 쿼리도 3번 호출될 것을 기대했는데,
위와 같은 방법으로 Lazy Loading을 방지해주었음에도 여전히 쿼리 호출횟수는 5번입니다.
아래와 같이 호출이 되는 데요.
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.
1. member 테이블에서 특정 id로 회원 정보를 조회 (memberRepository)
2. member 테이블에서 특정 id로 회원 정보를 조회(1번과 같음) (memberRepository)
3. name 컬럼을 조건으로 회원 정보를 조회 (memberRepository)
4. reservation 테이블에서 회원의 예약 정보와 관련된 데이터를 가져오는 쿼리 (reservationRepository)
5. waiting 테이블에서 회원의 대기열 정보를 가져오는 쿼리 (waitingRepository)
1. select m1_0.id, m1_0.email, m1_0.name, m1_0.password, m1_0.role from member m1_0 where m1_0.id=?;
2. select m1_0.id, m1_0.email, m1_0.name, m1_0.password, m1_0.role from member m1_0 where m1_0.id=?;
3. select m1_0.id, m1_0.email, m1_0.name, m1_0.password, m1_0.role from member m1_0 where m1_0.name=?
4. select r1_0.id, r1_0.date, m2_0.id, m2_0.email, m2_0.name, m2_0.password, m2_0.role, r1_0.name, t1_0.id, t1_0.description, t1_0.name, t2_0.id, t2_0.time_value from reservation r1_0 left join member m2_0 on m2_0.id=r1_0.member_id left join theme t1_0 on t1_0.id=r1_0.theme_id left join time t2_0 on t2_0.id=r1_0.time_id where r1_0.member_id=?;
5. select w1_0.id, w1_0.date, w1_0.member_id, w1_0.theme_id, w1_0.time_id, (select count(w2_0.id) from waiting w2_0 where w2_0.theme_id=w1_0.theme_id and w2_0.date=w1_0.date and w2_0.time_id=w1_0.time_id and w2_0.id<w1_0.id) from waiting w1_0 where w1_0.member_id=?;
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.
같은 쿼리가 반복 호출되는 경우 불필요한 중복 쿼리일 가능성이 있다고 들었는 데 제가 보기엔 아무리 봐도 반복호출될 실마리가 보이지 않거든요 ㅜㅠㅜ
대체 어디서 중복이 발생하여 같은 쿼리가 2번 발생하는 걸까요? 중복이 아니면 대체 어디서 문제가 발생하는 건지 궁금합니다 ㅜㅠ
(조심스런 내 예상)
3 ~ 5번까지가 아래의 코드와 관련된 쿼리들인 거 같고
1 ~ 2번은 페이지를 업데이트 할 때 이 페이지에 로그인하고 있는 사람이 누군지 확인을 하는 과정인가?
그렇다면 혹시 토큰으로 회원정보를 확인하는 과정에서 같은 쿼리가 2번 수행되는 건 아닐까?
public List<MyReservationResponse> findMyReservationsByName(String name) {
Member member = memberRepository.findByName(name)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다."));
final List<Reservation> reservations = reservationRepository.findByMemberId(member.getId());
final List<WaitingWithRank> waitingWithRanks = waitingRepository.findWaitingsWithRankByMemberId(member.getId());
return MyReservationResponse.of(reservations, waitingWithRanks);
}
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.
<질문 2>
여우님이 의도하신 N+1문제 해결방식이 @EntityGraph
어노테이션 이용이 맞나요?
다른 더 효율적인 방법이 있다면 알려주세요!!
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.
<새롭게 알게 된 점>
hibernate
란?
객체와 데이터베이스 테이블의 매핑 정보를 기반으로 SQL을 동적으로 생성함
덕분에 개발자는 SQL을 작성하지 않고, 객체를 다루는 코드로 작업할 수 있음
sql문을 개발자가 직접 작성하지 않아도 hibernate
덕분에 특정 데이터에 접근할 수 있던 것이군요!
hibernate는 이번에 처음 들어보는 개념이였는 데 여우님 덕분에 새롭게 알게되었습니다!! 감사합니다 :)
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.
<질문1>
select m1_0.id, m1_0.email, m1_0.name, m1_0.password, m1_0.role from member m1_0 where m1_0.id=?;
이 SQL쿼리는 위 메서드에서 2번 호출이 되고 있는데요. 대체 왜지?!?! 저도 아무리 디버깅 해봐도 모르겠습니다. 짱 신기하네요.
<질문2>
맞습니다! 의도한 대로 잘 수행해 주셨어요.
N+1 문제의 해결 방법은 정말 다양합니다. fetch join, entity graph, batch size 등 다양한 기술을 이용해 해결할 수 있지만
저것들을 다 아는 것보다 더 중요한 것은
의도한 것보다 훨씬 많은 쿼리가 발생했을 때 '아 N+1이 발생했구나!' 라는 것을 알아차리는 것입니다.
문제를 정의할 수 있다면 해결방법은 금방 찾으니까요!
<새롭게 알게 된 점>
멋진 지식을 배우셨군요!
우리가 사용하는 JPA(Java persistence API)는 자바에서 기본으로 제공하는 인터페이스입니다.
자바로 공부하면서 우리는 인터페이스를 만들어 사용하지만
인터페이스 혼자서는 어떤 기능을 하지 못하고, 인터페이스의 구현체가 그 기능을 구현하여 수행한다는 사실을 알고 있을 거에요.
JPA라는 인터페이스도 수많은 구현체들을 가지고 있는데, 그 구현체들 중 하나가 바로 hibernate에요!
hibernate 외에 있는 또 다른 JPA의 구현체들로는
EclipseLink, OpneJPA, DataNucleus 등이 있습니다. 잘 안 써요! 절대다수가 hibernate를 씁니다.
reservationRepository.findByDateAndThemeIdAndTimeId(reservationRequest.getDate(), theme.getId(), time.getId()) | ||
.ifPresent(it -> { | ||
throw new IllegalArgumentException("이미 예약된 시간입니다."); | ||
}); | ||
} |
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.
예약 날짜, 테마, 예약 시간 이 세 정보가 동시에 일치하면 안된다는 제약조건을 만들어 주셨군요 ! :D
이와 동일한 제약조건을 데이터베이스 레벨에서도 걸어줄 수 있습니다.
date, time_id, theme_id를 하나로 묶어 유니크 제약을 만들어주는 거에요.
JPA의 엔티티에서 유니크 제약을 만들려면 어떻게 할 수 있을까요?
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.
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"date", "theme_id", "time_id"})})
public class Reservation {
.
.
}
위와 같이 @Table
의 uniqueConstraints
속성을 이용해 date, time_id, theme_id조합이 항상 unique하도록 만들어주었습니다!!
같은 조합 발생 시 에러 발생하는 거까지 확인했습니다!!
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.
데이터베이스 레벨에서도 제약조건을 걸어줄 수 있을 거라고 생각은 했지만 구체적인 방법은 몰랐는 데
여우님 덕분에 구체적인 방법까지 알게 되었네요!! 감사합니다 :)
안녕하세요:) 여우 리뷰어님!! 처음 만나뵙게 되어 반갑습니다!!
워낙 실력이 뛰어나시다고 전해들어서 많이 기대가 되네요 : ) 잘 부탁드립니다☘️
<구현과정>
4단계 - Jpa전환
5단계 - 내 예약 목록 조회
6단계 - 예약 대기 기능
<궁금한 점>
auth파일과 util파일에 있는 클래스들(5개)의 위치가 적당한 지 한 번만 봐주실 수 있나요?
제가 처음 Interceptor와 Argument Resolver를 사용해 봤는데요. 특히 이들이 어디에 위치해 있어야 하는 지 너무 헷갈립니다.
어노테이션 활용 부족
스프링에 익숙해 지려고 노력하고 있지만 개인적으로 아직 어노테이션 활용능력이 아쉽다고 느껴집니다.
제가 작성한 코드에서 어노테이션을 적절히 활용할 수 있었던 부분이 있다면 피드백 부탁드립니다.
그리고 자주 사용하는 어노테이션들을 간단하게 언급주시면 제가 공부해보겠습니다!!
ReservationService, WaitingService의 레포지토리 의존성
현재 제 코드에서 ReservationService, WaitingService가 아래와 같이 많은 레포지토리를 가지고 있는데요.
현재 제 지식 수준에서는 저 레포지토리들을 다 들고와서 데이터를 꺼내쓰는 방법 밖에 생각이 안 납니다 ㅜㅠ
뭔가 제 생각엔 이게 비효율적인 방법 같은 데 막상 다른 대안이 잘 생각나지 않습니다.
제 코드에서 레포지토리 의존성을 줄일 수 있는 방법이 있나요? 제 방법이 최선이라면 그 이유는 무엇인 지 궁금합니다!!