프록시 기초
em.find() vs em.getReference()
- em.find() : 데이터베이스를 통해 실제 엔티티 객체 조회
- em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
- 하이버네이트가 내부적으로 라이브러리를 사용해서 가짜 엔티티 객체를 준다
프록시 특징
- 실제 클래스를 상속 받아서 만들어짐
- 하이버네이트가 내부적으로 라이브러리를 사용해서 만든다.
- 그러다보니 실제 클래스와 겉모양이 같다.
- 사용하는 입장에서는 구분하지 않고 사용하면 된다.
- 프록시 객체는 실제 객체의 참조(Target)을 보관한다.
- 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
- 프록시 객체는 처음 사용할 때 한번만 초기화
- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 변경되는 것이 아니다.
- 따라서 타입 체크할 때 주의해야한다.
- 비교가 안되기 때문에 instance of 사용해야 한다.
- 코드를 통해 살펴보자
-
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()); System.out.println("a == a: " + (m1 == reference)); // 실행 결과 Hibernate: /* insert hellojpa.Member */ insert into Member (createDateTime, INSERT_MEMBER, UPDATE_MEMBER, lastModifiedDate, TEAM_ID, USERNAME, MEBMER_ID) values (?, ?, ?, ?, ?, ?, ?) Hibernate: select member0_.MEBMER_ID as MEBMER_I1_3_0_, member0_.createDateTime as createDa2_3_0_, member0_.INSERT_MEMBER as INSERT_M3_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_.createDateTime as createDa2_7_1_, team1_.INSERT_MEMBER as INSERT_M3_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_.MEBMER_ID=? // 여기서 m1도 hellojpa.Member // reference도 hellojpa.Member // 이 두가지가 같은 경우는 우선 영속성 컨텍스트 1차캐시에 이미 실제 엔티티인 Member가 올라가있기 때문 // 굳이 영속성컨텍스트가 디비에 들릴 필요가 없다는 뜻 (성능최적화를 위해) m1 = class hellojpa.Member reference = class hellojpa.Member // JPA에서는 같은 영속성 컨텍스트 안에서는 항상 같아야 한다. // JPA에서는 자바 컬렉션에서 가져오는 것처럼 항상 true를 만족해 줘야한다. (JPA 기본 매커니즘) a == a: true // 위와는 반대의 경우일 때 Member refMember = em.getReference(Member.class, member1.getId()); System.out.println("refMember = " + refMember.getClass()); // 결과 : refMember = class hellojpa.Member$HibernateProxy$BVEj80y6 Member findMember = em.find(Member.class, member1.getId()); System.out.println("findMember = " + findMember.getClass()); // 결과 : findMember = class hellojpa.Member$HibernateProxy$BVEj80y6 // em.find 했을 때 실제 Member 엔티티를 가져올 거라고 생각되었지만.. // 프록시가 만들어진다. 즉 em.find해도 프록시를 만들수도 있다는 뜻이다. // 위에서 프록시를 이미 만들어졌는데 JPA에서는 == 은 true를 만족시켜야하기때문에 프록시를 호출하는 것이다. System.out.println("a == a: " + (refMember == findMember));
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면, em.getReference()를 호출해도 실제 엔티티 반환한다.
- 영속성 컨텍스트에 찾는 엔티티가 없는 경우에 DB에 들려서 실제 엔티티를 만들고 target과 연결한다.
프록시의 동작과정
// 이 코드가 동작하는 과정을 살펴보자
// getReference를 통해 호출하고 아래에서 getName()을 호출했을 때..
Member member = em.getReference(Member.class, "id1");
member.getName();
- getName()이라고 호출하게 되면
- 처음에 Member target에 값이 없기때문에 JPA가 영속성 컨텍스트에 요청을 한다.
- 영속성 컨텍스트가 DB를 조회해서 실제 Member 엔티티 객체를 만든다.
- 만들어진 진짜 엔티티를 target과 연결해준다.
- 연결된 target은 실제 엔티티와 연결되어 사용한다.
// 아래와 같이 코드를 작성하고 직접 호출하게 되면..
// 처음에 만들어지는 findMember는 프록시 객체이다.
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember.username = " + findMember.getUsername());
System.out.println("findMember.username = " + findMember.getUsername());
실행 결과
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(createDateTime, INSERT_MEMBER, UPDATE_MEMBER, lastModifiedDate, TEAM_ID, USERNAME, MEBMER_ID)
values
(?, ?, ?, ?, ?, ?, ?)
// getUsername()을 호출하는 순간
// JPA가 영속성 컨텍스트에 요청해서 디비에 들려서 실제 객체를 만들라고 요청한다.
// 그리고 target과 연결시켜준다.
// 아래의 결과가 그 내용이다.
Hibernate:
select
member0_.MEBMER_ID as MEBMER_I1_3_0_,
member0_.createDateTime as createDa2_3_0_,
member0_.INSERT_MEMBER as INSERT_M3_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_.createDateTime as createDa2_7_1_,
team1_.INSERT_MEMBER as INSERT_M3_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_.MEBMER_ID=?
// hello가 두번찍히는 이유는
// 위에서 영속성 컨텍스트를 통해 실제 엔티티를 생성한 상태이고
// target과 연결되어있기 때문에 값을 가져올 수 있는 것이다.
findMember.username = hello
findMember.username = hello
프록시 확인
- 프록시 인스턴스의 초기화 여부 확인 : PersistenceUnitUtil.isLoaded(Object entity)
- 프록시 클래스 확인 방법 : entity.getClass().getName() 출력
- 프록시 강제 초기화 : org.hibernate.Hibernate.initialize(entity);
'Dev > JPA' 카테고리의 다른 글
RS) MappedSuperclass 매핑 정보 상속(상속관계X) (0) | 2022.02.22 |
---|---|
RS) 상속관계 매핑 (0) | 2022.02.22 |
RS) 다대다 관계 (0) | 2022.02.17 |
RS) 연관관계 매핑시 주의할 점 (0) | 2022.02.15 |
RS) 양방향 연관관계와 연관관계의 주인 (0) | 2022.02.06 |