꿀똥벌레
꿀똥벌레 개발 블로그
꿀똥벌레
전체 방문자
오늘
어제
  • 분류 전체보기 (90)
    • JAVA (17)
    • SPRING (14)
    • Elasticsearch (4)
    • GRADLE (2)
    • HTML, CSS (0)
    • JAVASCRIPT (0)
    • GIT (1)
    • Vue.js (1)
    • server (1)
    • Python (0)
    • IT리뷰 (0)
    • 인프라 (6)
    • IOS (21)
    • 디자인패턴 (20)
    • Kafka (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • elasticsearch
  • 스프링 인테그레이션
  • SWIFT
  • KEEPALIVE
  • persistent connection
  • connectionRequestTimeout
  • java
  • 인덱스 템플릿
  • maxConnPerRoute
  • Index
  • springintegration
  • 스프링 인티그레이션
  • Index Template
  • ES
  • mappings
  • spring integration
  • spring
  • maxConnTotal
  • 엘라스틱서치
  • persistence connection

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
꿀똥벌레

꿀똥벌레 개발 블로그

JAVA

JPA 성능 최적화

2021. 4. 28. 13:38

JPA 성능최적화

  1. N+1 문제

    fetchType 이 Eager 로 설정 되어 있는 엔티티를 jpql로 조회할때 발생한다.

@Entity
public class Member {
    ...
    @OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
    private List<Order> orders = new ArrayList<Order>();
}

...

List<Member> members = em.createQuery("select m from Member m", Member.class)
        .getResultList();
//먼저 Member 들을 조회하는 쿼리를 수행 후 엔티티에 할당하는데
//할당하는 동중에 FetchType.Eager 를 발견하게 되고, 각 MemberId마다 Order조회 쿼리를 수행한다.
//해당 문제는 지연로딩이라 해도 동일하다.

해결법 1. fetch join 사용

select m from Member m join fetch m.orders

해결법 2. 하이버네이트 @BatchSize

  • 연관된 엔티티를 조회할 때 지정된 사이즈 만큼 in절을 이용해서 조회한다.
@Entity
public class Member {
    ...
    @org.hibernate.annotations.BatchSize(size = 5)
    @OneToMany(mappedBy="member", fetch=FetchType.EAGER)
    private List<Order> orders = new ArrayList<Order>();
}
//SELECT * FROM ORDERS WHERE MEMBER_ID IN(?, ?, ?, ?, ?);

해결법 3. 하이버네이트 @Fetch(FetchMode.SUBSELECT)

@org.hibernate.annotations.Fetch(FetchMode.SUBSELECT)

//SELECT O FROM ORDERS O WHERE O.MEMBER IN (SELECT M.ID FROM....)

베스트는 왠만하면 지연로딩을 사용하도록 하고, 필요시 jpql fetch join 을 사용하자.

@OneToOne, @ManyToOne — 디폴느는 즉시로딩

@OneToMany, @ManyToMany — 디폴트는 지연로딩

단순 조회 쿼리 성능 최적화

  1. 스칼라 타입으로 조회
select o.id, o.name from Order o
  1. 읽기 전용 쿼리 힌트 사용
TypedQuery<Order> query = em.createQuery("select o from Order o",
    Order.class);
query.setHint("org.hibernate.readOnly", true);
  1. 스프링 읽기전용 모드 설정
@Transactional(readOnly = true)//플러시 모드 MANUAL 설정

또는

@Transactional(propagation = Propagation.NOT_SUPPORTED)//트랜잭션 범위에서 제외
  • 1, 2번은 persistence context에서 스냅샷을 관리하지 않으므로 flush를 해도 DB에 반영되지 않는다.
  • 3번의 스프링 설정은 플러시 모드를 MANUAL로 설정하여, 플러시를 강제로 호출하지 않는 이상 플러시를 호출하지 않는다.
  • 트랜잭션 범위에서 제외하는 방법은 당연하게 데이터 수정에서 제외된다.
  • 스프링에서는 읽기전용 트랜잭션(readonly)와 읽기전용 엔티티 사용(setHint - readOnly) 를 사용하여 플러시도 작동하지 않고, 엔티티를 읽기전용으로 조회하도록 두가지 다 적용하는 것이 가장 효과적이다.

배치 처리

다건의 조회할 때 데이터를 전부 jpa 엔티티로 메모리에 실을 수 없기 때문에 페이징 처리를 해야 한다.

  1. JPA 페이징 처리
...
int pageSize = 100;
for(int i=0; i<10; i++) {
    List<Product> resultList = em.createQuery("select p from Product p",
            Product.class)
            .setFirstResult(i * pageSize)
            .setMaxResults(pageSize)
            .getResultList();

    for(Product product: resultList) {
        product.setPrice(product.getPrice() + 100);
    }
}
...
  1. 하이버네이트 scroll 사용 (DB 커서 사용)
EntityTransaction tx = em.getTransaction();
//하이버네이트 기능을 사용하기 위해 세션을 가져온다.
Session session = em.unwrap(Session.class);
tx.begin();
//scroll을 이용해서 엔티티를 하나씩 얻어올 수 있다.
ScrollableResults scroll = session.createQuery("select p from Product p")
        .setCacheMode(CacheMode.IGNORE) //2차 캐시 비활성화
        .scroll(ScrollMode.FORWARD_ONLY);

while(scroll.next()) {
    Product p = (Product) scroll.get(0);
    p.setPrice(p.getPrice() + 100);

    count++;
    if(count % 100 == 0) {
        /100개 단위로 영속화
        session.flush();session.clear();
    }
}

tx.commit();
session.close();

'JAVA' 카테고리의 다른 글

JPA 캐시  (0) 2021.04.28
JPA 락 처리  (0) 2021.04.28
JPA 엔티티 그래프  (0) 2021.04.28
JPA 엔티티 생명주기에 리스너 등록  (0) 2021.04.28
JPA Entity와 DB값 간 매핑  (0) 2021.04.28
    꿀똥벌레
    꿀똥벌레
    개발자 꿀똥벌레 입니다.

    티스토리툴바