JPA

프록시 객체와 지연 로딩, 즉시 로딩 - 1

방구석 대학생 2020. 10. 16. 18:34

"인프런 - 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 작성한 글 입니다."

www.inflearn.com/course/ORM-JPA-Basic#

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 프로그

www.inflearn.com

 

프록시 객체를 사용함에 있어, 지연 로딩 LAZY 를 사용해 연관관계 에 있는 도메인 클래스의 데이터를 따로 조회해 줄 수 있다.

Member 와 Team 도메인 클래스와 같이 다대일, 또는 일대다 연관관계에 있는 클래스에서 @ManyToOne 과 같은 어노테이션에 fetch 속성으로 FetchType.LAZY 를 지정해주면 연관관계에 있는 다른 도메인 클래스의 데이터를 프록시 객체로 생성시킨다. 

 

즉, find 메소드로 데이터를 조회할 시 Member 클래스만 우선적으로 DB 에서 조회하고 Team 클래스 데이터의 경우, 강제 호출과 같이 데이터를 직접 호출하는 경우에 select 쿼리를 DB로 보내서 데이터를 가져온다는 뜻이다.

 

Member - Team 연관관계에서 Team 객체 참조에 LAZY 속성을 부여함으로서 team 객체를 프록시 객체로 만들어주면 find 와 같이 Member 객체를 찾을 때 프록시 객체로 지정된 team 클래스의 데이터를 제외하고 Member 클래스의 데이터에 대해서만 DB 에 select 쿼리를 전달하여 데이터를 가져오게 된다.

 

- Member.java

@ManyToOne(fetch = FetchType.LAZY) // Team 클래스의 데이터를 프록시 객체로 조회하게 하는 속성
@JoinColumn
private Team team;

- JpaMain.java

Member m = em.find(Member.class, member1.getId());

- hibernate SQL

Hibernate: 
    select
        member0_.MEMBER_ID as member_i1_3_0_,
        member0_.createdBy as createdb2_3_0_,
        member0_.createdDate as createdd3_3_0_,
        member0_.lastModifiedBy as lastmodi4_3_0_,
        member0_.lastModifiedDate as lastmodi5_3_0_,
        member0_.team_TEAM_ID as team_tea7_3_0_,
        member0_.USERNAME as username6_3_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?

 

Team 클래스의 객체가 프록시로 만들어졌음을 확인하기 위해 이번엔 아래와 같은 코드를 작성하고 애플리케이션을 실행해보자.

- JpaMain.java

Team team = new Team();
team.setName("teamA");

em.persist(team); // Team 객체 데이터 삽입 및 영속성 컨텍스트 persist

Member member1 = new Member();
member1.setUsername("member1");
member1.setTeam(team); // Member 데이터에 Team 클래스 데이터 설정

em.persist(member1);

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

// find 메소드를 통해 Member 데이터를 찾기 위한 select 쿼리 전달
Member m = em.find(Member.class, member1.getId());

System.out.println("m = " + m.getTeam().getClass()); // 프록시 객체로 생성된 Team 클래스 객체 확인

System.out.println("======================");
m.getTeam().getName(); // Team 객체 데이터를 강제 호출함으로서 select 쿼리 전달
System.out.println("======================");

 

- hibernate SQL

Hibernate: // Team 데이터 삽입
    /* insert hellojpa.Team
        */ insert 
        into
            Team
            (createdBy, createdDate, lastModifiedBy, lastModifiedDate, name, TEAM_ID) 
        values
            (?, ?, ?, ?, ?, ?)
Hibernate: // Member 데이터 삽입
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (createdBy, createdDate, lastModifiedBy, lastModifiedDate, team_TEAM_ID, USERNAME, MEMBER_ID) 
        values
            (?, ?, ?, ?, ?, ?, ?)
Hibernate: // find 메소드를 통한 Member 데이터 select 
    select
        member0_.MEMBER_ID as member_i1_3_0_,
        member0_.createdBy as createdb2_3_0_,
        member0_.createdDate as createdd3_3_0_,
        member0_.lastModifiedBy as lastmodi4_3_0_,
        member0_.lastModifiedDate as lastmodi5_3_0_,
        member0_.team_TEAM_ID as team_tea7_3_0_,
        member0_.USERNAME as username6_3_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?
m = class hellojpa.Team$HibernateProxy$47LdzmQ1 // 프록시 객체 확인
=================
Hibernate: // 프록시 객체 강제 호출(실제 team 데이터를 사용하는 시점에 영속성 컨텍스트로 초기화 요청을 하면서 DB에 select 쿼리가 전달된다.)
    select
        team0_.TEAM_ID as team_id1_7_0_,
        team0_.createdBy as createdb2_7_0_,
        team0_.createdDate as createdd3_7_0_,
        team0_.lastModifiedBy as lastmodi4_7_0_,
        team0_.lastModifiedDate as lastmodi5_7_0_,
        team0_.name as name6_7_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?
=================

 

위의 SQL 문을 보면 Team 클래스의 객체는 LAZY 로 지정되어 있기 때문에 프록시 객체(지연 로딩)로 생성되는 것을 알 수 있다.

 

비즈니스 로직상 Member 에 있는 로직만 애플리케이션 으로 출력하는 경우가 많다면 위와 같이 가능 경우가 맞을 것이다.

그런데 반대로 Member 와 Team 을 동시에 쓰는 경우가 많다면 지연 로딩을 쓰면 오히려 효율이 떨어지게 된다.

그럴경우 FetchType.EAGER 로 속성을 변경하여 즉시 로딩으로 설정해주면, Member 데이터를 find 를 통해 찾는 경우 Team 데이터까지 함께 찾아오게 된다.

 

즉, Team 클래스의 객체를 프록시 객체로 생성하지 않고 곧바로 실제 Entity 를 사용하게 된다.

- Member.java

@ManyToOne(fetch = FetchType.EAGER) 
@JoinColumn
private Team team;

 

이번엔 Main 클래스의 코드를 다음과 같이 바꾼 후 애플리케이션을 실행해보자. 

- JpaMain.java

System.out.println("======================");
System.out.println("teamName = " + m.getTeam().getName()); 
System.out.println("======================");

설정을 위와 같이 EAGER 로 바꾼 후 애플리케이션을 실행하면 아래와 같은 hibernate SQL 을 출력하는 것을 알수 있다.

- hibernate SQL

Hibernate: 
    /* insert hellojpa.Team
        */ insert 
        into
            Team
            (createdBy, createdDate, lastModifiedBy, lastModifiedDate, name, TEAM_ID) 
        values
            (?, ?, ?, ?, ?, ?)
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (createdBy, createdDate, lastModifiedBy, lastModifiedDate, team_TEAM_ID, USERNAME, MEMBER_ID) 
        values
            (?, ?, ?, ?, ?, ?, ?)
Hibernate: // find 메소드를 통해 Member 데이터를 조회할 때 Team 데이터 까지 함께 조회한다.
    select
        member0_.MEMBER_ID as member_i1_3_0_,
        member0_.createdBy as createdb2_3_0_,
        member0_.createdDate as createdd3_3_0_,
        member0_.lastModifiedBy as lastmodi4_3_0_,
        member0_.lastModifiedDate as lastmodi5_3_0_,
        member0_.team_TEAM_ID as team_tea7_3_0_,
        member0_.USERNAME as username6_3_0_,
        team1_.TEAM_ID as team_id1_7_1_,
        team1_.createdBy as createdb2_7_1_,
        team1_.createdDate as createdd3_7_1_,
        team1_.lastModifiedBy as lastmodi4_7_1_,
        team1_.lastModifiedDate as lastmodi5_7_1_,
        team1_.name as name6_7_1_ 
    from
        Member member0_ 
    left outer join
        Team team1_ 
            on member0_.team_TEAM_ID=team1_.TEAM_ID 
    where
        member0_.MEMBER_ID=?
m = class hellojpa.Team // Team 클래스의 객체가 실제 Entity 로 생성되었다.
================= // 강제 호출이 되더라도 이미 쿼리가 전달되어 데이터를 가져왔기 때문에 따로 select 쿼리가 나오지 않게 된다.
teamName = teamA
=================

 

- 즉시 로딩(EAGER) 일 경우 JPA 구현체는 가능하면 테이블 조인을 사용해서 SQL 을 한번에 조회해서 데이터를 가져온다.

 

 

 

 

다음 글에선 즉시 로딩에 대한 주의점과 지연 로딩 기능의 활용에 대해 알아보자.