본문 바로가기
자바/JPA

[JPA/Java] JPQL 패치 조인의 한계 - 글로벌 배치 패치 사이즈, @BatchSize

by drCode 2023. 5. 11.
728x90
반응형

JPQL 패치 조인의 한계

① 패치 조인 대상에는 별칭을 줄 수 없다.

    - 하이버네이트는 가능하나, 가급적 사용하지 않는다.

jpql = "select t from Team t join fetch t.members m"; // 패치 조인 대상에는 Alias를 주면 안된다.

패치 조인은 연관관계를 다 끌고 오는 건데, 별칭을 줘서 데이터를 누락시켜서 가져오는 가능성도 있다.

패치 조인에서 별칭을 쓰지 않아야 하는 이유는

 → 데이터의 정합성이나 객체 사상이 안맞을 가능성이 높기 때문에

 

쓴다면 다대일 관계로 만들고 쓰는게 좋다.

jpql = "select m from Member m join fetch m.team";

 

② 둘 이상의 컬렉션은 패치 조인을 사용할 수 없다. → 데이터 정합성 문제 때문에 (일대다대다)

 

③ 컬렉션을 패치 조인하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다.

    - 일대일, 다대일 같은 단일 값 연관 필드들은 패치 조인해도 페이징은 가능하다.

    - 하이버네이트는 경고 로그를 남기고 메모리에서 페이징을 처리한다. (매우 위험)

 

jpql = "select t from Team t join fetch t.members m";
List<Team> result = em.createQuery(jpql, Team.class)
        .setFirstResult(0)
        .setMaxResults(2)
        .getResultList();

페이징API를 적용한 fetch join 결과

페이징 API를 적용하였지만, 실제로 나간 쿼리에는 페이징이 적용되지 않았다.

 

메모리 상에서만 적용되었다고 경고 로그를 확인할 수 있다.

 

페이징을 그래도 써야한다면

 

jpql = "select t from Team t"; // 즉시 로딩
List<Team> result = em.createQuery(jpql, Team.class)
        .setFirstResult(0)
        .setMaxResults(2)
        .getResultList();

지연 로딩으로 쿼리를 3번 호출하게 된다.

이렇게 되면 성능이 많이 떨어진다.

 

데이터가 많을수록 성능이 떨어진다.

 

이를 방지하기 위해서

 

Team.java

@BatchSize(size = 100) 을 추가해준다.

@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();

즉시 로딩 결과로, 연관된 모든 엔티티 내용을 탐색하여 가져온다.

 

한번에 팀A, 팀B에 들어가는 모든 내용을 다 가져오는 결과를 가져온다.

 

글로벌하게 배치사이즈를 설정해두는 방법도 있다.

 

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="hello">
        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/jpaStudy2"/>
<!--            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>-->
            <property name="hibernate.dialect" value="dialect.MyH2Dialect"/>
            <!--<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle12cDialect"/>-->

            <!-- 옵션 -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
            <property name="hibernate.jdbc.batch_size" value="10"/>
            <property name="hibernate.hbm2ddl.auto" value="create" />
            <!-- 배치 사이즈 글로벌 세팅 -->
            <property name="hibernate.default_batch_fetch_size" value="100"/>
        </properties>
    </persistence-unit>
</persistence>

 

 

<property name="hibernate.default_batch_fetch_size" value="100"/>

 

이렇게 property를 추가해주면 된다.

 

해당 클래스에서 컬렉션 형태로 데이터를 저장하는 필드에 @BatchSize 를 지정해주거나

글로벌하게 배치 패치 사이즈를 지정해주면 연관된 엔티티들을 SQL 한번에 조회할 수 있다.

 

이렇게 함으로써 성능 최적화를 도모할 수 있다

 

@OneToMany(fetch = FetchType.LAZY)  // 글로벌 로딩 전략

패치 조인을 적용한 것은 글로벌 로딩 전략보다 우선한다.

 

실무에서 글로벌 로딩 전략은 모두 지연로딩이다.

 

그래서, 최적화가 필요한 곳은 패치 조인을 적용해야한다.

 

※ 정리

(1) 모든 것을 패치 조인으로 해결할 수는 없다.

(2) 패치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다.

(3) 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 한다면,

패치 조인보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적이다.

 

728x90
반응형

댓글