본 글은 인프런의 김영한님 강의 자바 ORM 표준 JPA 프로그래밍 - 기본편
을 수강하며 기록한 필기 내용을 정리한 글입니다.
-> 인프런
-> 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의
(GETTER, SETTER 생략)
< Member >
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "MEMBER_NAME")
private String name;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
< Team >
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
@Column(name = "TEAM_NAME")
private String name;
}
Team team = new Team();
team.setName("team1");
entityManager.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team);
entityManager.persist(member);
entityManager.flush();
entityManager.clear();
Member findMember = entityManager.find(Member.class, member.getId());
EntityManager.getReference()
메소드를 통해 직접 생성할 수 있다.Member findMember = entityManager.find(Memeber.class, member.getId()); // 실제 엔티티를 반환한다.
Member findMember = entityManager.getReference(Member.class, member.getId()); // 프록시 객체를 반환한다.
EntityManager.getReference()
메소드로 Member 클래스의 프록시 객체를 생성할 수 있다.Member findMember = entityManager.getReference(Member.class, member.getId());
System.out.println("Class : " + findMember.getClass());
System.out.println("id : " + findMember.getId());
System.out.println("name : " + findMember.getName());
Class : class velogbasic.Member$HibernateProxy$rtp2fw8A
을 통해 프록시 객체로 설정된 것을 확인할 수 있다.EntityManger.getReference()
메소드로 생성된 Member 프록시 객체는 빈 껍데기로만 이루어져 있다가 실제로 name 이라는 필드 값이 활용될 때 DB를 조회한다.
- getName()이 호출된다.
- 프록시가 아직 초기화 되지 않은 상태이므로, 영속성 컨텍스트에 프록시 초기화 요청을 전달한다.
- 영속성 컨텍스트가 DB에 조회쿼리를 전달하여 데이터를 가져온다.
- 가져온 데이터로 실제 Member 엔티티를 생성한다.
- Member 프록시 객체의 target에 실제 Member 엔티티가 설정되고, 참조를 통해 Member 엔티티의 getName()이 호출된다.
-> target.getName()
< 식별자를 조회할 때 프록시 초기화가 일어나지 않는 이유 >
AbstractLazyInitializer
클래스의 초기화 로직에 의해 이루어 진다.@Override
public final Serializable getIdentifier() {
if (isUninitialized() && isInitializeProxyWhenAccessingIdentifier() ) {
initialize();
}
return id;
}
hibernate.jpa.compliance.proxy
설정 값을 true로 해야함. /**
* Main constructor.
*
* @param entityName The name of the entity being proxied.
* @param id The identifier of the entity being proxied.
* @param session The session owning the proxy.
*/
protected AbstractLazyInitializer(String entityName, Object id, SharedSessionContractImplementor session) {
this.entityName = entityName;
this.id = id;
// initialize other fields depending on session state
if ( session == null ) {
unsetSession();
}
else {
setSession( session );
}
}
AbstractLazyInitializer
생성자에서 id 값을 저장해 두는 것을 확인할 수 있다.org.hibernate.LazyInitializationException
예외가 발생한다.< 타입 특징 1. ==
비교 안된다. instance of
를 사용해야 한다. >
Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.find(Member.class, member2.getId());
System.out.println("m1 == m2 : " + (m1.getClass() == m2.getClass())); // true
find()
메소드가 활용되었으므로 둘 다 실제 엔티티 객체이며, 당연히 ==
비교가 가능하다.Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, member2.getId());
System.out.println("m1 == m2 : " + (m1.getClass() == m2.getClass())); // false
==
으로 비교할 경우 false가 나온다.instanceof
를 활용해야 한다.Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, member2.getId());
System.out.println("m1 : " + (m1 instanceof Member)); // true
System.out.println("m2 : " + (m2 instanceof Member)); // true
instanceof
를 써야 한다.private static void isSame(Member m1, Member m2) {
System.out.println("m1 : " + (m1 instanceof Member));
System.out.println("m2 : " + (m2 instanceof Member));
}
< 타입 특징 2. 같은 영속성 컨텍스트 내 동일성 보장으로 인한 특징 >
같은 영속성 컨텍스트 내에서 동일한 pk 값을 갖는 엔티티 객체는
==
비교를 했을 때 무조건true
가 반환되어야 한다.
즉, 하나의 영속성 컨텍스트 내에서 같은 식별자를 갖는 엔티티 객체는 무조건 ==
비교가 성립되어야 한다는 것이다.
다음 두 경우를 두고 비교해 보자.
경우 1 : find() -> getReference()
Member findMember1 = entityManager.find(Member.class, member1.getId());
Member findMember2 = entityManager.getReference(Member.class, member1.getId());
System.out.println("findMember1 Class : " + findMember1.getClass());
System.out.println("findMember2 Class : " + findMember2.getClass());
System.out.println("findMember1 == findMember2 : " + (findMember1 == findMember2));
findMember1과 findMember2는 모두 같은 식별자의 데이터를 조회하고 있다.
findMember1에서 먼저 find() 메소드를 통해 조회가 이루어지고, 이는 실제 Member 엔티티로 findMember1에 담긴다.
findMember2에서 getReference() 메소드에 의해 영속성 컨텍스트를 조회하게 되고, 실제 Member 엔티티를 그대로 받아 저장한다.
따라서 두 객체 모두 실제 Member 엔티티 객체가 되며, 결과는 다음과 같다.
경우 2 : getReference() -> find()
Member findMember1 = entityManager.getReference(Member.class, member1.getId());
Member findMember2 = entityManager.find(Member.class, member1.getId());
System.out.println("findMember1 Class : " + findMember1.getClass());
System.out.println("findMember2 Class : " + findMember2.getClass());
System.out.println("findMember1 == findMember2 : " + (findMember1 == findMember2));
getReference()
메소드를 통해 프록시 객체가 담길 것이다.find()
메소드를 통해 실제 엔티티가 생성될 것이다.==
비교가 가능할 것이다.find()
메소드로 조회한 findMember2 역시 프록시 객체가 되어버린다.getReference() -> find()
과정은 둘 다 프록시 객체가 된다.EntityManagerFactor.getPersistenceUnitUtil().isLoaded()
Member findMember1 = entityManager.getReference(Member.class, member1.getId());
// false
System.out.println("is Loaded : " + entityManagerFactory.getPersistenceUnitUtil()
.isLoaded(findMember1));
findMember1.getName();
// true
System.out.println("is Loaded : " + entityManagerFactory.getPersistenceUnitUtil()
.isLoaded(findMember1));
org.hibernate.Hibernate.initialize()
Member findMember1 = entityManager.getReference(Member.class, member1.getId());
// false
System.out.println("is Loaded : " + entityManagerFactory.getPersistenceUnitUtil()
.isLoaded(findMember1));
Hibernate.initialize(findMember1);
// true
System.out.println("is Loaded : " + entityManagerFactory.getPersistenceUnitUtil()
.isLoaded(findMember1));
즉시 로딩과 지연 로딩이 본 프록시 기능을 활용하여 구현된다.