반응형
1. 프록시
Member를 조회할 때 Team도 함께 조회해야 할까?
- 회원과 팀 함께 출력
public void printUserAndTeam(String memberId) {
Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
System.out.println("회원 이름: " + member.getUsername());
System.out.println("소속팀: " + team.getName()); }
- 회원만 출력
public void printUser(String memberId) {
Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
System.out.println("회원 이름: " + member.getUsername());
}
회원과 팀을 함께 출력하기 위해서는 Member와 Team을 모두 조회해야 하지만, 회원만 출력할 때도 Team을 조회해야 할까?
프록시 기초
- em.find() vs em.getReference()
- em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
- em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
em.find() 예시
- 코드
try {
Member member = new Member();
member.setUsername("hello");
em.persist(member);
em.flush();
em.clear();
//
Member findMember = em.find(Member.class, member.getId());
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " + findMember.getUsername());
tx.commit();
}
- 결과
Hibernate:
select
member0_.MEMBER_ID as MEMBER_I1_3_0_,
member0_.INSERT_MEMBER as INSERT_M2_3_0_,
member0_.createdDate as createdD3_3_0_,
member0_.UPDATE_MEMBER as UPDATE_M4_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_.INSERT_MEMBER as INSERT_M2_7_1_,
team1_.createdDate as createdD3_7_1_,
team1_.UPDATE_MEMBER as UPDATE_M4_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=?
findMember.id = 1
findMember.username = hello
em.getReference() 예시 1
- 코드
try {
Member member = new Member();
member.setUsername("hello");
em.persist(member);
em.flush();
em.clear();
//
// Member findMember = em.find(Member.class, member.getId());
Member findMember = em.getReference(Member.class, member.getId());
// System.out.println("findMember.id = " + findMember.getId());
// System.out.println("findMember.username = " + findMember.getUsername());
tx.commit();
}
- 결과
Hibernate:
call next value for hibernate_sequence
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, USERNAME, MEMBER_ID)
values
(?, ?, ?, ?, ?, ?)
em.getReference() 예시 2
- 코드
Member member = new Member();
member.setUsername("hello");
em.persist(member);
em.flush();
em.clear();
//
// Member findMember = em.find(Member.class, member.getId());
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " + findMember.getUsername());
tx.commit();
}
- 결과
Hibernate:
select
member0_.MEMBER_ID as MEMBER_I1_3_0_,
member0_.INSERT_MEMBER as INSERT_M2_3_0_,
member0_.createdDate as createdD3_3_0_,
member0_.UPDATE_MEMBER as UPDATE_M4_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_.INSERT_MEMBER as INSERT_M2_7_1_,
team1_.createdDate as createdD3_7_1_,
team1_.UPDATE_MEMBER as UPDATE_M4_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=?
findMember.username = hello
프록시 특징
- 실제 클래스를 상속받아서 만들어진다.
- 실제 클래스와 겉 모양이 같다.
- 사용하는 입장에서는 진짜 객체인지, 프록시 객체인지 구분하지 않고 사용하면 된다.(이론상)
- 프록시 객체는 실제 객체의 참조(target)를 보관한다.
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
프록시 객체의 초기화
Member member = em.getReference(Member.class, “id1”);
member.getName();
프록시의 특징 정리
1. 프록시 객체는 처음 사용할 때 한 번만 초기화
2. 프록시 객체를 초기화할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
- 2번 추가 설명 - 프록시는 교체되는 것이 아니고 내부의 타겟에 값이 채워지는 것
try {
Member member = new Member();
member.setUsername("hello");
em.persist(member);
em.flush();
em.clear();
//
// Member findMember = em.find(Member.class, member.getId());
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("before findMember = " + findMember.getClass());
System.out.println("findMember.username = " + findMember.getUsername());
System.out.println("after findMember = " + findMember.getClass());
tx.commit();
}
Hibernate:
call next value for hibernate_sequence
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, USERNAME, MEMBER_ID)
values
(?, ?, ?, ?, ?, ?)
before findMember = class hellojpa.Member$HibernateProxy$0q4UoZqm
Hibernate:
select
member0_.MEMBER_ID as MEMBER_I1_3_0_,
member0_.INSERT_MEMBER as INSERT_M2_3_0_,
member0_.createdDate as createdD3_3_0_,
member0_.UPDATE_MEMBER as UPDATE_M4_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_.INSERT_MEMBER as INSERT_M2_7_1_,
team1_.createdDate as createdD3_7_1_,
team1_.UPDATE_MEMBER as UPDATE_M4_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=?
findMember.username = hello
after findMember = class hellojpa.Member$HibernateProxy$0q4UoZqm
3. 프록시 객체는 원본 엔티티를 상속받는다. 따라서 타입 체크시 주의해야한다.(== 비교 실패, 대신 instance of 사용)
- 3번 추가 설명 - em.find()와 em.find()
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()));
m1 == m2 -> true
- 3번 추가 설명 - em.find()와 em.getReference()
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()));
m1 == m2 -> False
→ 실제로 업무를 할 때는 객체가 실제로 넘어오는지, 프록시로 넘어오는지 알 수 없다. 따라서 절대로 == 비교는 해서는 안된다. instance of를 사용하라.
try {
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
em.persist(member2);
em.flush();
em.clear();
Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, member2.getId());
logic(m1, m2);
tx.commit();
}
...
private static void logic(Member m1, Member m2) {
System.out.println("m1 == m2 -> " + (m1 instanceof Member));
System.out.println("m1 == m2 -> " + (m2 instanceof Member));
}
m1 == m2 -> true
m1 == m2 -> true
4. 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다.
- 이유 1 - 영속성 컨텍스트에 있는데 굳이 프록시로 가져와봐야 아무 이점이 없다. 원본을 반환하는 것이 성능 최적화에서도 더 이점이 있다.
- 이유 2 - JPA에서는 자바 컬렉션에서 가져온 것을 == 비교하듯이 ==비교가 한 영속성 컨텍스트에서 가져오고 pk가 같으면 true를 반환한다.
- 4번 추가 설명 1
try {
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
em.flush();
em.clear();
Member m1 = em.find(Member.class, member1.getId());
System.out.println("m1 = " + m1.getClass());
Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference = " + reference.getClass());
tx.commit();
}
System.out.println("a == a -> " + (m1 == reference));
m1 = class hellojpa.Member
reference = class hellojpa.Member
a == a -> true
- 4번 추가 설명 2 - m1과 reference 모두 getReference를 사용해도 같은 프록시로 반환 → m1 == reference가 성립해야 하기 때문
try {
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
em.flush();
em.clear();
Member m1 = em.getReference(Member.class, member1.getId());
System.out.println("m1 = " + m1.getClass());
Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference = " + reference.getClass());
System.out.println("a == a -> " + (m1 == reference));
tx.commit();
}
m1 = class hellojpa.Member$HibernateProxy$tv7w3yGh
reference = class hellojpa.Member$HibernateProxy$tv7w3yGh
a == a -> true
- 4번 추가 설명 3 - 프록시로 한번 조회가 되면 em.find()에서도 프록시로 반환을 한다. == 비교를 맞추기 위해서.
- 핵심은 프록시든 아니든 개발에 문제가 없게 하는 것이 중요함. 어떻게든 JPA에서는 refMember == findMember 비교를 true로 맞추려 한다.
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());
Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember = " + findMember.getClass());
System.out.println("a == a -> " + (refMember == findMember));
tx.commit();
}
refMember = class hellojpa.Member$HibernateProxy$LMOvPJAY
Hibernate:
select
member0_.MEMBER_ID as MEMBER_I1_3_0_,
member0_.INSERT_MEMBER as INSERT_M2_3_0_,
member0_.createdDate as createdD3_3_0_,
member0_.UPDATE_MEMBER as UPDATE_M4_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_.INSERT_MEMBER as INSERT_M2_7_1_,
team1_.createdDate as createdD3_7_1_,
team1_.UPDATE_MEMBER as UPDATE_M4_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=?
findMember = class hellojpa.Member$HibernateProxy$LMOvPJAY
a == a -> true
5. 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생한다.
(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터뜨린다.)
- 5번 추가 설명 - detach(), close(), clear()를 사용할 시 영속성 컨텍스트가 날라감 → refMember는 영속성 컨텍스트의 도움을 받지 못함 → LazyInitializationException: could not initialize proxy 오류
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()); // Proxy
em.detach(refMember); // 준영속
// em.close();
System.out.println("refMember = " + refMember.getUsername());
tx.commit();
}
Hibernate:
call next value for hibernate_sequence
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, USERNAME, MEMBER_ID)
values
(?, ?, ?, ?, ?, ?)
refMember = class hellojpa.Member$HibernateProxy$1XjTqsMB
org.hibernate.LazyInitializationException: could not initialize proxy [hellojpa.Member#1] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:169)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:309)
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$1XjTqsMB.getUsername(Unknown Source)
at hellojpa.JpaMain.main(JpaMain.java:30)
Jun 23, 2023 9:46:22 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]
프록시 확인
1. 프록시 인스턴스의 초기화 여부 확인 - emf.getPersistenceUnitUtil().isLoaded(refMember));
- 초기화 전 isLoaded = false
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()); // Proxy
System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
tx.commit();
}
Hibernate:
call next value for hibernate_sequence
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, USERNAME, MEMBER_ID)
values
(?, ?, ?, ?, ?, ?)
refMember = class hellojpa.Member$HibernateProxy$HQDEJLw0
isLoaded = false
- 초기화 후 isLoaded = true
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()); // Proxy
refMember.getUsername();
System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
tx.commit();
}
Hibernate:
call next value for hibernate_sequence
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, USERNAME, MEMBER_ID)
values
(?, ?, ?, ?, ?, ?)
refMember = class hellojpa.Member$HibernateProxy$KSgjPxUU
Hibernate:
select
member0_.MEMBER_ID as MEMBER_I1_3_0_,
member0_.INSERT_MEMBER as INSERT_M2_3_0_,
member0_.createdDate as createdD3_3_0_,
member0_.UPDATE_MEMBER as UPDATE_M4_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_.INSERT_MEMBER as INSERT_M2_7_1_,
team1_.createdDate as createdD3_7_1_,
team1_.UPDATE_MEMBER as UPDATE_M4_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=?
isLoaded = true
2. 프록시 클래스 확인 방법 - refMember.getClass().getName() 출력
System.out.println("refMember = " + refMember.getClass().getName()); // Proxy
System.out.println("refMember = " + refMember.getClass().getName()); // Proxy
3. 프록시 강제 초기화 - JPA 표준은 강제 초기화가 없다. Hibernate.initialize(refMember); 이렇게 사용
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + refMember.getClass().getName()); // Proxy
Hibernate.initialize(refMember); // 강제 초기화
Hibernate:
select
member0_.MEMBER_ID as MEMBER_I1_3_0_,
member0_.INSERT_MEMBER as INSERT_M2_3_0_,
member0_.createdDate as createdD3_3_0_,
member0_.UPDATE_MEMBER as UPDATE_M4_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_.INSERT_MEMBER as INSERT_M2_7_1_,
team1_.createdDate as createdD3_7_1_,
team1_.UPDATE_MEMBER as UPDATE_M4_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=?
반응형
'TIL' 카테고리의 다른 글
[TIL-10 / 230628] JPA 값 타입(임베디드 타입, 값 타입 컬렉션) (0) | 2023.06.28 |
---|---|
[TIL-9 / 230627] 프록시 - 즉시로딩과 지연로딩, 고아 객체 (0) | 2023.06.27 |
[TIL-7 / 230622] JPA 다양한 연관관계 매핑과 고급매핑 (0) | 2023.06.23 |
[TIL-6 / 230621] JPA 다양한 연관관계 매핑 (0) | 2023.06.21 |
[TIL-5 / 230620] JPA 연관관계 매핑 기초, 연관관계의 주인 (0) | 2023.06.20 |