프록시와 연관관계 매핑 - 3
"인프런 - 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 작성한 글 입니다."
www.inflearn.com/course/ORM-JPA-Basic#
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 프로그
www.inflearn.com
* 이번엔 프록시의 또다른 자세한 특징과 프록시 확인을 위한 유틸리티 메소드들에 대해 알아보자.
영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생한다.
우선 아래와 같은 코드를 통해 프록시 객체를 영속성 컨텍스트를 통해 초기화 해주자.
- JpaMain.java
Member refMember = em.getReference(Member.class, member1.getId()); // 프록시 객체 생성
System.out.println("refMember = " + refMember.getClass()); // 프록시 객체로 생성되었음을 확인
// 프록시 객체가 실제로 사용되면서 초기화(영속성 컨텍스트를 통해 DB에 쿼리를 전달하게 된다.)
refMember.getUsername();
- hibernate SQL
refMember = class hellojpa.Member$HibernateProxy$cCfc7xC4 // getClass() 출력
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_ID as team_id7_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_ID=team1_.TEAM_ID
where
member0_.MEMBER_ID=?
그런데 만약 위의 코드에서 em.detach(refMember); 와 같이 영속성 컨텍스트에서 객체를 떼어낸 다음
refMember.getUsername(); 과 같이 영속성 컨텍스트 에서 떼어낸 프록시 객체에 대한 초기화를 요청하면 어떤일이 발생할까?
(제대로된 오류 출력을 위해 catch 문에 e.printStackTrace(); 를 작성해준다.)
- JpaMain.java
try{
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
em.flush();
em.clear();
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + refMember.getClass());
em.detach(refMember); // 영속성 컨텍스트에서 프록시 객체를 분리해낸다(준영속 상태로 전환)
refMember.getUsername();
tx.commit();
} catch(Exception e){
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
위와 같이 찾아온 프록시 객체를 영속성 컨텍스트 에서 분리한 후 getUsername 과 같은 메소드를 통해 초기화 하려고 하면 아래와 같은 오류가 발생하게 된다.(em.close() 나 em.clear() 를 통해 영속성 컨텍스트를 닫거나 날려버려도 아래와 같은 오류가 발생한다.)
org.hibernate.LazyInitializationException: could not initialize proxy [hellojpa.Member#1] - no Session // 프록시를 초기화 할 수 없다.
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:170)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:310)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at hellojpa.Member$HibernateProxy$Gp9MfT2S.getUsername(Unknown Source)
- 보통 트랜잭션이 시작하고 끝날 때 영속성 컨텍스트도 시작하고 끝나게 라이프 사이클을 맞춘다.
그래서 트랜잭션이 끝나고 나서 프록시를 조회하려하면 위와 같은 오류가 발생하는 것을 자주 볼 수 있다.
프록시 확인 유틸리티 메소드
* PersistenceUnitUtil.isLoaded(Object entity)
- 프록시 인스턴스의 초기화 여부 확인 : 프록시가 초기화 되었음을 알려준다.
- JpaMain.java
System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
- EntityManagerFactory 클래스에서 지원하는 메소드이다.
- 초기화 되지 않은 프록시 객체를 확인할 경우 아래와 같이 false 를 출력한다.
isLoaded = false
- 위의 메소드가 호출되기 전에 refMember.getUsername(); 과 같이 프록시 객체를 초기화 시키면 아래와 같이 true 를 반환한다.
isLoaded = true
* entity.getClass().getName() 출력
- 프록시 클래스임을 확인하는 메소드이다.
- refMember.getClass() 또는 refMember.getClass().getName() 을 호출해서 출력하는 것으로 프록시 객체임을 확인할 수 있다.
* Hibernate.initialize(entity);
- 프록시를 강제로 초기화 시키는 메소드이다.
- 사실 refMember.getUsername() 과 같은 경우도 강제 초기화라고 할 수 있으나 조금 애매하다고 볼 수 있다.
- 그래서 Hibernate 는 Hibernate.initialize(); 를 제공한다.
- Hibernate 에서 제공하는 것으로 JPA 표준에는 강제 초기화가 없다.
- JpaMain.java
Member refMember = em.getReference(Member.class, member1.getId());
Hibernate.initialize(refMember); // 프록시 강제 초기화
- hibernate SQL
Hibernate: // 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_ID as team_id7_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_ID=team1_.TEAM_ID
where
member0_.MEMBER_ID=?
위의 Hibernate SQL 을 보면 select 쿼리를 호출하는 것을 통해서 프록시 객체를 강제로 초기화 시키는 것을 볼 수 있다.
다음 글에서는 프록시 객체에서의 즉시 로딩(EAGER)과 지연 로딩(LAZY)에 대해 알아보자.