Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

DOing

[JPA] 프록시(Proxy) 본문

JPA

[JPA] 프록시(Proxy)

mangdo 2021. 7. 14. 14:36

프로젝트에 연관관계를 적용하려고 하니, 즉시 로딩과 지연로딩의 문제와 cascade 문제가 연달아 터졌다.

이 둘을 제대로 이해하기 위해서 프록시 개념을 이해해보고 넘어가려고 한다.


👻 프록시

: 가짜 Entity 객체

1. 실제 Entity를 상속받아 만들어지기 때문에 겉모양은 실제 Entity와 같지만, 안에가 비어있다.

  : 상속은 하이버네이트가 내부적으로 프록시 라이브러리를 써서 만들어준다.

2. 사용자 입장에서는 진짜 객체인지, 프록시 객체인지 구분하지 않고 사용하면 된다.(이론상)

3. 프록시 객체에는 target이라는 실제 객체의 참조를 보관하고 있다.

   그래서 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출

 

👻 프록시가 실제 객체를 호출하는 과정

1. 사용자가 member.getName() 호출

2. Member target 값이 없으면, JPA가 영속성 컨텍스트에 이것을 요청한다. (진짜 Member 객체 가져와!!)

   => 영속성 컨텍스트의 초기화 요청 : 프록시에 값이 없을 때 진짜 객체 가져와라!

   => 초기한번만 함

3. 영속성 컨텍스트가 DB조회

4. 실제 Member Entity 생성해서 준다.

5. target에 실제 Member Entity를 연결해준다.

6. target을 이용해서 member.getName() 응답

 

 

👻 프록시의 특징

1. 프록시 객체는 처음 사용할때 한번만 초기화

2. 프록시 객체가 실제 엔티티로 교체되는 것이 아니다!

   : 초기화가 되면 프록시는 유지되고 내부의 target이 채워지면서 프록시 객체를 통해서 실제 엔티티에 접근 가능

3. 프록시 객체는 원본 엔티티를 상속받기 때문에 타입체크시 주의해야한다.

   : member1.getClass()==member2.getClass() 비교가 아니고 instance of를 사용해야한다.

4. 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다.

   : 영속성컨텍스트에 이미 엔티티가 있는데 뭐하러 프록시 만드냐? 그냥 엔터티반환하는게 성능 최적화가 좋다.

   : JPA에서는 이게 프록시든 객체이든 다음과 같은 코드에서 true를 반환해야한다.

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());// 이것도 객체, 프록시x

System.out.println("a == a: " + (m1 == reference)); // true로 보장!!!

순서를 바뀌면 다음과 같다. 어찌되었든 JPA는 두개가 같음을 보장해준다.

즉 사용자 입장에서는 진짜 객체인지, 프록시 객체인지 구분하지 않고 믿고 사용하면 된다.

 

Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);

em.flush();
em.clear();

Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference = " + reference.getClass());// 프록시

Member m1 = em.find(Member.class, member1.getId());
System.out.println("m1 = " + m1.getClass()); // 이것도 프록시 객체x

System.out.println("a == a: " + (m1 == reference)); // true로 보장!!!

 

5. 영속석 컨텍스트의 도움을 받을 수 없는 준영속 상태일때, 프록시를 초기화하면 문제가 발생한다.

 

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.close(); 영속성 컨텍스트 끔
// em.detach(refMember); refMember를 영속성컨텍스트에서 끄집어낸다.
em.clear(); // 영속성 컨텍스트를 깨끗하게 지운다.

refMember.getUsername(); // 에러발생
// refMember가 영속성 컨텍스트의 도움을 못받는 상태 = 프록시가 작동을 못한다.

-> 실무에서 자주 발생하는 에러이다.

@Transactional이 시작하고 끝날 때, 보통 영속성 컨텍스트도 시작하고 끝나게 된다. 끝나고 나서 프록시를 조회하면 no session하면서 에러를 발생시킨다.

 

 

👻 프록시 확인

 

1. 프록시 클래스 확인 + 강제 초기화

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());// 프록시
refMember.getUsername(); // 프록시 강제 초기화

// 하이버네이트 강제 초기화 : Hibernate.initialize(refMember)
// JPA는 그런 거없음 그냥 refMember.getUsername();

 

 

 

 

출처

: 인프런 강의 - 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편