Ian's Archive 🏃🏻

Profile

Ian

Ian's Archive

Developer / React, SpringBoot ...

📍 Korea
Github Profile →
Categories
All PostsAlgorithm19Book1C1CI/CD2Cloud3DB1DesignPattern9ELK4Engineering1Front3Gatsby2Git2IDE1JAVA7JPA5Java1Linux8Nginx1PHP2Python1React9Security4SpatialData1Spring26
thumbnail

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화

JPA
2022.09.27.

Series

  • 1자바 ORM 표준 JPA 프로그래밍 (기본편)
  • 2실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
  • 3실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
  • 4실전 스프링 데이터 JPA
  • 5Querydsl

Entity Mapping

양방향 매핑 설정

jackson라이브러리는 json생성 방법에 대해 모름 (무한 루프)

  1. 양방향 매핑관계 설정 시 한쪽에 @JsonIgnore 처리 해야 함
    -> 실제로 Property에 Null 할당하는 방식
  2. @JsonBackReference // 부모 클래스 , @JsonManagedReference // 자식 클래스 -> 순환참조 방어
  3. Entity 대신 DTO 사용

Entity 사용시 문제

엔티티를 반환값에 직접 사용 할 경우 XtoOne 관계(LASY 로딩일 경우)에서 객체가 없기 때문에 하이버네이트가 프록시 객체 생성
-> Jackson 라이브러리가 값을 읽다 문제 발생

해결 방법

hibernate5Module 사용

  1. 강제 지연 로딩 설정
  2. 강제로 연관관계 객체를 초기화

결론 : 엔티티를 외부로 노출하지 말고 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 스펙에 맞춘 코드가 리포지토리에 들어감

성능 튜닝 권장 순서

  1. Entity -> DTO 변환
  2. fetch join으로 최적화 (대부분 성능 이슈 해결)
  3. DTO로 직접 조회
  4. 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: 하이버네이트

  1. true

    • 최초 데이터베이스 커넥션 시작 시점부터 API 응답이 끝날 때 까지 영속성 컨텍스트와 데이터베이스 커넥션을 유지
    • Filter Interceptor, Controller, View, Service, Repository
  2. off

    • 트랜잭션을 종료할 때 영속성 컨텍스트를 닫고, 데이터베이스 커넥션도 반환
    • Service, Repository
    • 모든 지연로딩을 트랜잭션 안에서 처리
      • @Transactional 어노테이션 가진 Service 계층에서 처리
      • 핵심 비즈니스 로직화면이나 API맞춘 서비스 부분을 분리하는게 좋다.

Tip!!

  • lombok 사용 시 Entity에 비해 DTO는 더 유연하게 사용
    (중요한 로직이 많이 들어가 있지 않음)

  • 비즈니스 로직 구성 시 쿼리와 커맨드를 분리 (CQS 패턴)

  • 캐시가 필요한 경우에는 DTO를 캐시

Reference

Previous Post
실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
Next Post
실전 스프링 데이터 JPA
Thank You for Visiting My Blog, I hope you have an amazing day 😆
© 2023 Ian, Powered By Gatsby.