프로그래밍/Spring

[ JPA ] 페치 조인 (fetch join) - 한계

Yanoo 2022. 2. 2. 12:35
728x90
반응형

<인프런의 김영한님의 강의를 보고 정리한 내용입니다>

 

 

🎁 페치 조인의 특징과 한계

  • 페치 조인 대상에는 별칭을 줄 수 없다.
    • 하이버네이트는 가능, 가급적 사용X 여기서 마지막 m을 말함
    • String query = "select t From Team t join fetch t.members m";​
  • 둘 이상의 컬렉션은 페치 조인 할 수 없다.
  • 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.
    • 일대일, 다대일 같은 단일 값 연관 필드는 페치 조인해도 페이징 가능
      String query = "select t From Team t join fetch t.members m";​
      은 문제가 될 수 있지만,
      String query = "select m From Member m join fetch m.team t";​
      는 다대일이기 때문에 페이징에 문제 없다.
    • 하이버네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험)

 

페이징을 하기 위해서 다른 방법도 있는데, batch size를 이용하는 것이다.

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try {

            Team teamA = new Team();
            teamA.setName("팀A");
            em.persist(teamA);

            Team teamB = new Team();
            teamB.setName("팀B");
            em.persist(teamB);

            Member member1 = new Member();
            member1.setUsername("회원1");
            member1.setTeam(teamA);
            em.persist(member1);

            Member member2 = new Member();
            member2.setUsername("회원2");
            member2.setTeam(teamA);
            em.persist(member2);

            Member member3 = new Member();
            member3.setUsername("회원3");
            member3.setTeam(teamB);
            em.persist(member3);

            em.flush();
            em.clear();
            String query = "select t From Team t";

            List<Team> result = em.createQuery(query, Team.class)
                    .setFirstResult(0)
                    .setMaxResults(2)
                    .getResultList();

            for (Team team : result) {
                System.out.println("team = " + team.getName() + "|members=" + team.getMembers().size());
            }

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }
        emf.close();
    }
}

이렇게 페이징 API를 쓴다고 하면, 현재는 LAZY 로딩이기에

프록시로 가져왔다가 for문 안에 들어올 때, 멤버에 대한 데이터를 들고오기에

팀을 select하고 멤버에 대한 쿼리가 따로 진행된다.

이 때, TEAM 엔티티에서 @BatchSize를 추가해준다.

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

이렇게 하고 결과를 봐보면,

이렇게 멤버 쿼리가 하나만 실행됐다. 여기서

where
        members0_.TEAM_ID in (
            ?, ?
        )

이 부분의 ? 수 만큼의 팀을 조회한 것이다. 즉 batchSize 만큼 팀에 대한 쿼리를 넘기는 것.

 

이 BatchSize 설정은 엔티티 이외에도 persistence.xml에

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

를 추가해서도 사용할 수 있다.

 


 

728x90
반응형