JPA

프록시와 연관관계 매핑 - 3

방구석 대학생 2020. 10. 16. 17:59

"인프런 - 자바 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)에 대해 알아보자.