본문 바로가기
JAVA/JPA

[Spring + JPA] JPA 즉시로딩과 지연로딩 정리

by 개미가되고싶은사람 2024. 11. 18.

목차

     

     

    즉시로딩(Eager)

    즉시로딩은 데이터를 조회할 때, 부모 엔티티를 가져올때 관련된 자식 엔티티도 함께 가져오는 방식으로 작동합니다.

    추가적으로 @ManyToOne, @OneToOne의 fetch 속성에 기본값은 즉시로딩으로 설정되어 있으며, 즉시로딩을 사용하려면 아래와 같이 작성하면 됩니다.

    @xxToxx(fetch = fetchType.EAGER)

     

    즉시로딩 사용 시 간단한 로그

    Hibernate: 
        select
            member0_.MEMBER_ID as MEMBER_I1_0_0_,
            member0_.TEAM_ID as TEAM_ID3_0_0_,
            member0_.USERNAME as USERNAME2_0_0_,
            team1_.TEAM_ID as TEAM_ID1_1_1_,
            team1_.name as name2_1_1_ 
        from
            Member member0_ 
        left outer join
            Team team1_ 
                on member0_.TEAM_ID=team1_.TEAM_ID 
        where
            member0_.MEMBER_ID=?

     

    지연로딩(Lazy)

    지연 로딩은 즉시 로딩과 반대로 필요한 최소한의 엔티티만 가져오는 방식으로, 부모 엔티티를 조회할 때 자식 엔티티를 가져오지 않습니다. 대신 자식 엔티티에 접근하는 경우에는 추가적으로 필요한 쿼리를 실행하여 데이터를 가져옵니다.  @OneToMany와 @ManyToMany는 기본적으로 지연 로딩을 사용하며, 지연 로딩을 사용하려면 아래와 같이 작성하면 사용할 수 있습니다.

    @xxToxx(fetch = FetchType.LAZY)

     

    지연로딩 사용 시 로그

    Hibernate: 
        select
            team0_.TEAM_ID as TEAM_ID1_1_0_,
            team0_.name as name2_1_0_ 
        from
            Team team0_ 
        where
            team0_.TEAM_ID=?

     

    그렇다면 어떤 전략을 사용하는게 더 좋을까??

     

    결론만 말씀드리면 웬만하면 지연로딩을 사용하는게 더 좋습니다. 왜냐하면 즉시로딩을 사용하면 예상하지 못한 문제가 발생할 수 있습니다.

     

    즉시로딩 사용 시 문제점

    N+1 문제

    N+1 문제는 데이터베이스에서 데이터를 조회할 때 성능 이슈로 한 개의 쿼리 조회 시 N개의 쿼리가 발생하는 문제입니다. 예를 들어, 다음과 같은 상황을 가정해 보겠습니다.

     

    테이블 구조: Member 테이블이 있고, 하나의 Team에 여러 개의 사용자(Member)를 가질 수 있습니다.

    @Entity
    public class Member {
    
        @Id @GeneratedValue
        @Column(name = "MEMBER_ID")
        private Long id;
        
        private String username;
    
        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "team_id")
        Team team;
    }
    
    @Entity
    public class Team {
    
        @Id @GeneratedValue
        @Column(name = "MEMBER_ID")
        private Long id;
        
        private String teamname;
    }


    상황:  모든 사용자를 조회 - 코드에서는 추가 쿼리 1개만 발생

            try {
                Team team = new Team();
                team.setName("teamTest");
                em.persist(team);
    
                Member member = new Member();
                member.setName("test");
                member.setTeam(team);
                em.persist(member);
    
                em.flush();
                em.clear();
    
                List<Member> members = em.createQuery("select m from Member m", Member.class)
                        .getResultList();
    
                tx.commit();
    }

     

    모든 Member를 조회하는 쿼리를 실행하면 처음에는 Member에 대한 데이터만 가져옵니다. 그러나 즉시로딩으로 설정된 설정된 Member와 Team 간의 관계 때문에 각 Member에 대해 연관된 Team 정보를 가져오기 위해 추가 쿼리가 발생하게 되어 총 N+1개의 쿼리가 실행됩니다.

    예를 들어, 3개의 Team이 존재하고 5명의 Member가 있으면 처음에 1개의 쿼리로 모든 Member를 가져오고 그 다음 5명의 Member가 속했는데 3개의 Team에 대해 쿼리가 3개 추가 실행되어 총 1 + 3 = 4개의 쿼리가 실행됩니다. 이렇게 N+1문제로 추가적인 쿼리가 발생해 성능 저하가 발생합니다.

    정리

    첫 번째 쿼리: 한 번의 쿼리로 Member 조회
    추가 쿼리 : 엔터티에 연관된 엔터티의 개수 만큼의 추가 쿼리를 수행

     

    예상치 못한 조인 쿼리 생성

    즉시로딩을 사용하면 한 개의 테이블에서 여러 테이블 간 조인을 사용할 때 성능 저하가 생길 수 있는데, 이는 즉시로딩이 연관된 모든 테이블을 한 번에 가져오는 방식이기 때문에 조인한 테이블의 수가 많아질수록 쿼리가 복잡해지기 때문입니다.

     

     

    참조

    https://woo-chang.tistory.com/28

    https://kkkdh.tistory.com/entry/JPA-%ED%94%84%EB%A1%9D%EC%8B%9C#%EC%A6%89%EC%8B%9C-%EB%A1%9C%EB%94%A9%EA%B3%BC-%EC%A7%80%EC%97%B0-%EB%A1%9C%EB%94%A9

    https://velog.io/@jinyeong-afk/%EA%B8%B0%EC%88%A0-%EB%A9%B4%EC%A0%91-%EC%A6%89%EC%8B%9C-%EB%A1%9C%EB%94%A9%EA%B3%BC-%EC%A7%80%EC%97%B0-%EB%A1%9C%EB%94%A9%EC%9D%98-%EC%B0%A8#%EC%A7%80%EC%97%B0%EB%A1%9C%EB%94%A9lazy

    https://velog.io/@jin0849/JPA-%EC%A6%89%EC%8B%9C%EB%A1%9C%EB%94%A9EAGER%EA%B3%BC-%EC%A7%80%EC%97%B0%EB%A1%9C%EB%94%A9LAZY

    https://sjh9708.tistory.com/160