

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
JPA
2022.09.27.
Series
Entity Mapping
양방향 매핑 설정
jackson라이브러리는 json생성 방법에 대해 모름 (무한 루프)
- 양방향 매핑관계 설정 시 한쪽에 @JsonIgnore 처리 해야 함
-> 실제로 Property에 Null 할당하는 방식 - @JsonBackReference // 부모 클래스 , @JsonManagedReference // 자식 클래스 -> 순환참조 방어
- Entity 대신 DTO 사용
Entity 사용시 문제
엔티티를 반환값에 직접 사용 할 경우 XtoOne 관계(LASY 로딩일 경우)에서 객체가 없기 때문에 하이버네이트가 프록시 객체 생성
-> Jackson 라이브러리가 값을 읽다 문제 발생
해결 방법
hibernate5Module 사용
- 강제 지연 로딩 설정
- 강제로 연관관계 객체를 초기화
결론 : 엔티티를 외부로 노출하지 말고 DTO 사용 (내부 컬랙션도 전부 DTO로 바꿔줘야 함 -> 바꿔주지 않으면 내부 스펙 노출)
DTO 사용 시 장점
- Entity와 API 스펙 분리 가능
- Entity가 변경될 경우 API 스펙 유지 가능
성능 튜닝
1. Entity를 DTO 변환 -> 패치 조인
XtoOne 관계 설정 시 N+1문제 발생
=> 페치 조인(fetch join)을 사용해서 쿼리 1번에 조회
public List<Order> findAllWithMemberDelivery() {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d", Order.class)
.getResultList();
}
2. DTO 직접 조회
=> select 절에 new를 사용해 JPQL 결과를 DTO로 즉시 변환
복잡한 쿼리를 DTO로 뽑아야 할 때 QueryService or QueryRepository로 빼서 작성
public List<OrderQueryDto> findOrderDtos() {
return em.createQuery(
"select new jpabook.jpashop.repositoryerQueryDto(o.id, m.name, o.orderDate, o.status,ddress) " +
"from Order o" +
" join o.member m" +
" join o.delivery d", OrderQueryDto.class)
.getResultList();
}
단점
- 리포지토리 재사용성 떨어짐
- API 스펙에 맞춘 코드가 리포지토리에 들어감
성능 튜닝 권장 순서
- Entity -> DTO 변환
- fetch join으로 최적화 (대부분 성능 이슈 해결)
- DTO로 직접 조회
- JPA Native SQL or JDBC Template으로 SQL직접 사용
컬렉션 조인 최적화
OneToMany 인 경우 distinct와 fetch join을 사용해 최적화
-> 컬렉션은 페치 조인시 페이징이 불가능
JPA의 distinct는 2가지 역할
- SQL에 distinct 추가
- 같은 엔티티가 조회되면 중복 제거
컬렉션 엔티티 사용 시 페이징 처리
1. 엔티티 직접 조회
- ToOne 관계 모두 페치 조인, 컬렉션 지연조인
- hibernate.default_batch_fetch_size 설정, @BatchSize 적용
- 개별 @BatchSize 적용 시 XtoOne - 엔티티, XtoMany(Collection) - 컬렉션 필드
2. DTO 직접 조회
-> ToOne(N:1, 1:1) 관계 조회
- ToMany(1:N) 관계 각각 별도로 처리 (개별 요소 마다 SQL로 데이터 가져와 객체에 추가)
- IN 절을 활용해서 메모리에 미리 조회해서 최적화
결론
-> IN 절 활용하자
OSIV와 성능 최적화
Open Session In View: 하이버네이트
-
true
- 최초 데이터베이스 커넥션 시작 시점부터 API 응답이 끝날 때 까지 영속성 컨텍스트와 데이터베이스 커넥션을 유지
- Filter Interceptor, Controller, View, Service, Repository
-
off
- 트랜잭션을 종료할 때 영속성 컨텍스트를 닫고, 데이터베이스 커넥션도 반환
- Service, Repository
- 모든 지연로딩을 트랜잭션 안에서 처리
- @Transactional 어노테이션 가진 Service 계층에서 처리
- 핵심 비즈니스 로직과 화면이나 API맞춘 서비스 부분을 분리하는게 좋다.
Tip!!
-
lombok 사용 시 Entity에 비해 DTO는 더 유연하게 사용
(중요한 로직이 많이 들어가 있지 않음) -
비즈니스 로직 구성 시 쿼리와 커맨드를 분리 (CQS 패턴)
- Command : 내부에서 데이터 변경(사이드 이펜트), 값 반환x / Query : 데이터 변경x 값 반환o
- 쓰기(insert, update) / 읽기(select) 분리 가능
- CQS(Command Query Separation) Pattern 이란?
-
캐시가 필요한 경우에는 DTO를 캐시