프록시 객체와 지연 로딩, 즉시 로딩 - 1
"인프런 - 자바 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 을 한번에 조회해서 데이터를 가져온다.
다음 글에선 즉시 로딩에 대한 주의점과 지연 로딩 기능의 활용에 대해 알아보자.