[Spring] JPA @EntityGraph

박준형·2023년 11월 14일
0

Spring

목록 보기
7/17
post-thumbnail

📌JPA에서 FetchType을 Lazy로 설정할 경우

✅문제 상황

Member

public class Member {

    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String username;

    private int age;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "team_id")
    private Team team;
}

Team

public class Team {

    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String name;

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

Memebr와 Team 객체가 N:1 관계에 있다.

이때 Member 객체를 조회한 후 연관관계에 있는 Team 객체를 사용한다면?

	@Test
    public void findMemberLazy() {
        //given
        //member1 -> teamA
        //member2 -> teamB

        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        teamRepository.save(teamA);
        teamRepository.save(teamB);

        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamB);
        memberRepository.save(member1);
        memberRepository.save(member2);

        em.flush();;
        em.clear();

        //when
        List<Member> members = memberRepository.findAll();

        for (Member member : members) {
            System.out.println("member = " + member);

  			//member.teamClass = class study.datajpa.Entity.Team$HibernateProxy$wL9kzFrK
            System.out.println("member.teamClass = " + member.getTeam().getClass());

            System.out.println("member.team = " + member.getTeam().getName());
        }
    }

FetchType이 Lazy이므로 JPA는 Team을 프록시 객체로 가져오게 된다.
이후에 Team 객체를 사용할 때 마다 DB로 조회 쿼리를 보내게 된다.
이것이 JPA에서 자주 발생하는 N + 1 문제이다.

✅해결 방안

쿼리 코드

// fetch를 하게 되면 join 후 select절에 team 객체 데이터도 포함해준다.
@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();

JPA가 보낸 쿼리

select member0_.member_id as member_i1_0_0_, team1_.team_id as team_id1_1_1_, member0_.age as age2_0_0_, member0_.team_id as team_id4_0_0_, member0_.username as username3_0_0_, team1_.name as name2_1_1_ 
from member member0_ 
left outer join team team1_ on member0_.team_id=team1_.team_id;

테스트 코드

	@Test
    public void findMemberLazy() {
        //given
        //member1 -> teamA
        //member2 -> teamB

        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        teamRepository.save(teamA);
        teamRepository.save(teamB);

        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamB);
        memberRepository.save(member1);
        memberRepository.save(member2);

        em.flush();;
        em.clear();

        //when 쿼리 수정
        List<Member> members = memberRepository.findMemberFetchJoin();

        for (Member member : members) {
            System.out.println("member = " + member);

  			//member.teamClass = class study.datajpa.Entity.Team
            System.out.println("member.teamClass = " + member.getTeam().getClass());

            System.out.println("member.team = " + member.getTeam().getName());
        }
    }

이렇게 해주면 Member와 연관관계에 있는 Team 객체를 fetch join 하여 실제 Team 객체를 생성하게 된다. 실제 Team 객체사 생성되었으므로 Team 객체를 사용할 때 마다 조회 쿼리를 보내지 않는다. JPA에선 이런식으로 N + 1 문제를 해결한다.


📌JPA가 제공하는 @EntityGraph 사용

// Data JPA가 제공하는 findAll을 override 해서 사용할 경우
@Override
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();

// JPQL로 직접 작성할 경우
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();

// Named 쿼리를 사용할 경우
@EntityGraph(attributePaths = {"team"})
List<Member> findEntityGraphByUsername(@Param("username") String username);

위 3가지 경우 모두 @EntityGraph를 사용할 수 있다. attributePaths 안에 명시한 연관관계 객체를 fetch join 해서 가져오게 된다.

profile
으쌰 으쌰

0개의 댓글

관련 채용 정보