JPA Lazy Loading Select문 실행시점

공병주(Chris)·2022년 8월 15일
0
post-thumbnail

우아한테크코스 팀 프로젝트에서 JPA를 사용하기로 했다.
JPA의 Lazy Loading에서 select문이 나가는 시점이 조금 헷갈려서, select 문이 실행되는 시점을 직접 확인해보았습니다.

엔티티(Post, Member)

@Entity
@Getter
public class Post {

    @Id @GeneratedValue(strategy = IDENTITY)
    private Long id;

    private String title;

    private String content;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    protected Post() {
    }

    public Post(String title, String content, Member member) {
        this.title = title;
        this.content = content;
        this.member = member;
    }
}
@Entity
@Getter
public class Member {

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

    private String name;

    @OneToMany(mappedBy = "member")
    private List<Post> posts = new ArrayList<>();

    protected Member() {
    }

    public Member(String name) {
        this.name = name;
    }
}

먼저 테스트해본 Entity에 대해 간단히 짚고 넘어가보겠습니다.

Post와 Member는 N:1 양방향 연관관계를 맺습니다.
두 엔티티는 서로에 대해 Lazy Loading을 하도록 설정해두었습니다.

test.sql

insert into member (name) values ('chris')
insert into post (content, title, member_id) values ('첫번째 글 제목', '첫번째 글 본문', 1)
insert into post (content, title, member_id) values ('두번째 글 제목', '두번째 글 본문', 1)

테스트를 위해서, 데이터를 먼저 insert 해주었고, 아래의 테스트에서 @sql을 통해서 Test 전에 실행되도록 했습니다.

@OneToMany에서의 Lazy

@DataJpaTest
@Sql("classpath:test.sql")
class MyTest {

    @Autowired
    private MemberRepository memberRepository;

    @Test
    void myTest() {
        Member found = memberRepository.findById(1L)
                .orElseThrow(IllegalArgumentException::new);

        System.out.println("-----------------Before getPosts()-----------------");
        List<Post> posts = found.getPosts();
        System.out.println("-----------------Before posts.get(0)-----------------");
        Post post = posts.get(0);
        System.out.println("-----------------Before posts.get(1)-----------------");
        Post post2 = posts.get(1);
        System.out.println(post.getContent());
        System.out.println(post2.getContent());
    }
}

실행결과

Hibernate: select member0_.member_id as member_i1_0_0_, member0_.name as name2_0_0_ from member member0_ where member0_.member_id=?
-----------------Before getPosts()-----------------
-----------------Before posts.get(0)-----------------
Hibernate: select posts0_.member_id as member_i4_1_0_, posts0_.id as id1_1_0_, posts0_.id as id1_1_1_, posts0_.content as content2_1_1_, posts0_.member_id as member_i4_1_1_, posts0_.title as title3_1_1_ from post posts0_ where posts0_.member_id=?
-----------------Before posts.get(1)-----------------
첫번째 글 제목
두번째 글 제목

위의 실행결과를 보면 found.getPosts()로 List를 조회해도 select문이 실행되지 않습니다. 이때까지는 List에 해당하는 프록시 객체만이 존재합니다.

그 후, posts.get(0)로 List<Post>의 요소를 조회할 때, select문이 실행되고 실제 객체들을 로딩합니다.

또한, 위의 테스트에서 알 수 있는 것은 select 문이 실행될 때, member_id를 가진 모든 Post들을 다 조회해옵니다. 두번째 sql문을 보아도 알 수 있고, posts.get(1)를 해도 추가적인 select문이 실행되지 않습니다.

@ManyToOne에서의 Lazy

ONE의 pk가 아닌 컬럼에 접근

@DataJpaTest
@Sql("classpath:test.sql")
class MyTest {

    @Autowired
    private MemberRepository memberRepository;

    @Test
    void myTest() {
        Post post = postRepository.findById(1L)
                .orElseThrow(IllegalArgumentException::new);
        System.out.println("-----------------Before getMember()-----------------");
        Member member = post.getMember();
        System.out.println("-----------------Before member.getName()-----------------");
        System.out.println(member.getName());
    }
}

실행결과

Hibernate: select post0_.id as id1_1_0_, post0_.content as content2_1_0_, post0_.member_id as member_i4_1_0_, post0_.title as title3_1_0_ from post post0_ where post0_.id=?
-----------------Before getMember()-----------------
-----------------Before member.getName()-----------------
Hibernate: select member0_.member_id as member_i1_0_0_, member0_.name as name2_0_0_ from member member0_ where member0_.member_id=?
chris

getMember로 Member 객체를 조회했을 때는 Proxy 객체 만이 존재합니다.

이후에 member의 content 필드를 조회할 때, select문이 실행되는 것을 알 수 있습니다.

  1. ONE의 pk에 접근
@DataJpaTest
@Sql("classpath:test.sql")
class MyTest {

    @Autowired
    private MemberRepository memberRepository;

    @Test
    void myTest() {
        Post post = postRepository.findById(1L)
                .orElseThrow(IllegalArgumentException::new);
        System.out.println("-----------------Before getMember()-----------------");
        Member member = post.getMember();
        System.out.println("-----------------Before member.getId()-----------------");
        System.out.println(member.getId());
    }
}

실행결과

Hibernate: select post0_.id as id1_1_0_, post0_.content as content2_1_0_, post0_.member_id as member_i4_1_0_, post0_.title as title3_1_0_ from post post0_ where post0_.id=?
-----------------Before getMember()-----------------
-----------------Before member.getId()-----------------
1

N:1에서 N은 1의 PK를 컬럼으로 가지고 있기 때문에, 1의 getId()를 호출해도 select문이 실행되지 않습니다.

끗.

0개의 댓글