사내에서 기술교육을 진행하여 정리하였다.
하이버네이트 버전
Java Persistence 2.2 (하이버네이트 5.3+)
- stream query results
- @Repeatable 어노테이션
Jakarta Persistence 3.1 (하이버네이트 6.1+)
- UUID 를 타입으로 쓸 수 있다.
- JPQL / Criteria API 의 확장 - 날짜, 숫자 함수 추가 등
- …
하이버네이트 는 2가지 버전을 운영중이다.
2023 03 14 기준 최신 버전
* Hibernate 5.6
* Hibernate 6.1 -> 자카르타를 쓰려면 이걸 써야함
로그
쿼리가 나가면 바인딩 값이 ? 로 떠서 볼 수가 없는데BasicBinder=trace
옵션을 application.yml 에 추가하면 바인딩 된 값을 볼 수 있다.
JPA
@Transient : 특정 필드를 컬럼에 맵핑하지 않을 경우에 지정
복합 Key
방법 1) @IdClass
방법 2) @EmbeddedId / @Embeddable
복합 Key Class 제약조건
- public class
- public 기본(no-arg) 생성자
- Serializable 인터페이스 구현
- equals(), hashCode() 메서드 정의
복합키 @JoinColumn 대신 @MapsId 를 사용하자
Fetch 전략
- FetchType.EAGER
- FetchType.LAZY
-ToOne 의 기본 전략은 EAGER 로 되어있어서 일반적인 상황에서 최적화를 하기 위해서는 ToOne 전략을 모두 LAZY 로 바꿔라
영속성 전이 (cascade)
연관관계에 있는 엔티티도 같이 저장할 수 있다.
이게 없으면 각각 insert 해야하는 불편함이 있음.
단점
엔티티 2개의 라이프사이클이 똑같을 수 없다.
ex) 업무와 댓글 -> 업무를 지울 때 댓글을 지워야할까?
하나의 업무에 댓글이 엄청 많이 달렸다. 업무를 지우면 댓글 지우는데 한세월… 성능이 엄청 안좋음
(댓글은 나중에 배치로 따로 지우거나 하는게 나을 수 있음)
insert 는 일반적인 상황에서 상관없지만, remove 시에는 성능을 고려해서 영속성 전이를 사용해야한다.
- CascadeType.ALL: 모든 Cascade를 적용 (성능 문제로인해 실무에서 사용 X)
- CascadeType.PERSIST: 엔티티를 영속화할 때, 연관된 엔티티도 함께 유지
- CascadeType.MERGE: 엔티티 상태를 병합(Merge)할 때, 연관된 엔티티도 모두 병합
- CascadeType.REMOVE: 엔티티를 제거할 때, 연관된 엔티티도 모두 제거
- CascadeType.DETACH: 부모 엔티티를 detach() 수행하면, 연관 엔티티도 detach()상태가 되어 변경 사항 반영 X
- CascadeType.REFRESH: 상위 엔티티를 새로고침(Refresh)할 때, 연관된 엔티티도 모두 새로고침
JPA 와 DB 간 패러다임 불일치
방향성
- 단방향 (unidirectional)
- 양방향 (bidirectional)
DB는 외래키에 방향이 없다.
자바는 방향이 있다.
- 양방향 연관 관계에서 주인은 외래 키 (FK) 가 있는 곳
- 연관 관계의 주인이 아닌 경우, mappedBy 속성으로 연관 관계의 주인을 지정한다.
단방향에 비해 양방향은 복잡하고 양방향 연관관계를 맵핑하려면 객체를 양쪽 모두에서 관리해야함
따라서 일단든 단방향 방식으로 사용하고 꼭 필요하다면 양방향으로 사용하자.
연관관계
일대다
의도와 별개로 나는 insert 만 했는데 update 쿼리가 발생하는 문제가 있다. (연관관계된 엔티티의 id 값을 update)
다에 해당하는 레코드가 많을수록 update 쿼리는 더 많이 발생한다.
영속성 전이
를 사용한다면 update 쿼리가 안나가지 않을까?
- 영속성 전이를 사용해도 동일하게 update 가 발생한다.
그럼 복합키
를 사용한다면 update 쿼리가 안나가지 않을까?
- 동일하게 update 가 발생한다.
해결
- 일대다 양방향 관리로 변경한다.
N + 1 문제
단건 조회의 경우에는 연관관계에 해당하도록 지연, 즉시 로딩으로 가져온다.
하지만 findAll 같이 다건 조회의 경우에는 연관된 녀석 말고 주 인 녀석만 findAll 쿼리를 날린다. (1)
그 후 어? 연관관계가 필요하네? 하고 연관된 N 개를 조회하는 쿼리를 레코드 개수만큼 N개 날린다.. (N)
LAZY 로딩으로 바꾼다고 하더라도 위 문제는 해결되지 않는다.
해결
- Fetch Join
- JPQL : join fetch
- Querydsl : fetchJoin()
- -> N 에 해당하는 부분을 join 하는 방법
- fetch 를 안쓰면 주 테이블에 대한 select 컬럼만 가져온다.
- fetch 를 쓰면 join 에 참여한 모든 select 컬럼을 가져온다. 그 후 연관관계의 그림을 보고 값을 채워준다. 그러므로 필요한 데이터가 모두 포함된 상태이므로 select 쿼리를 추가로 날릴 필요가 없다. (N 문제 해결) 단 쿼리가 뚱뚱해지니 무거움.
- Entity Graph
- 그 외
- 하이버네이트 @BatchSize
- 하이버네이트 @Fetch(FetchMode.SUBSELECT) : IN 절 안에 쿼리를 넣는 방법
- -> 쿼리가 너무 많이 발생되니까 묶어서 실행하자는 방법
N + 1 을 해결하려다 성능이 더 나빠지는 경우가 있다.
-> 여러 테이블을 Join 하니까 쿼리가 더 무거워짐.
이런 경우는 실행 계획을 떠서 N + 1 방식의 성능과 비교해서 트러블 슈팅을 해야한다.
Querydsl
JPQL
은 text 기반이라 실제 실행을 시켜봐야 에러가 발생하는지 확인이 가능하다는 단점이 있다.
그걸 해결하고자 나온게 Criteria API
인데, JPQL 을 생성하는 빌더 클래스로 나와서 동적 쿼리 작성이 쉽지만.. 사용하기가 너무 어렵고 복잡하다.
요걸 쉽게 만들어서 나온게 Querydsl
이다.
@NoRepositoryBean
Spring Data Jpa 에서는 repository 를 찾을 때, @Repository 가 없어도 된다.
보통 해당 어노테이션은 querydsl 의 Custom 인터페이스에 선언한다. (custom 인터페이스는 단순히 선언 용도이므로)
나는 Repository 빈 후보에서 빼줘’라는 의미
@NoRepositoryBean
public interface OrderRepositoryCustom {
List<Order> getOrdersWithAssociations();
}
public class OrderRepositoryImpl extends QuerydslRepositorySupport
implements OrderRepositoryCustom {
public OrderRepositoryImpl() {
super(Order.class);
}
@Override
public List<Order> getOrdersWithAssociations() {
QOrder order = QOrder.order;
QCustomer customer = QCustomer.customer;
QOrderItem orderItem = QOrderItem.orderItem;
QItem item = QItem.item;
return from(order)
.innerJoin(order.customer, customer).fetchJoin()
.leftJoin(order.orderItems, orderItem).fetchJoin()
.innerJoin(orderItem.item, item).fetchJoin()
.fetch();
}
}
'세미나' 카테고리의 다른 글
[우아한테크 세미나] 테크 리더 3인이 말하는 개발자 원칙 (0) | 2023.03.30 |
---|---|
[기술교육] JPA 트러블 슈팅 - 2편 (0) | 2023.03.15 |
if Kakao dev 2020 - 카카오 대 장애 회고 정리 (2) | 2022.12.07 |
코드리뷰 노하우 (0) | 2022.08.12 |
구글 로그인 연동으로 배우는 OAuth 인증 - 2 (0) | 2019.05.15 |
댓글