Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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
- Thymeleaf
- springdatajpa
- Spring Boot
- 백준
- db
- kotlin
- QueryDSL
- Servlet
- 자바
- 그리디
- AOP
- spring
- pointcut
- http
- jpa
- JPQL
- SpringBoot
- 김영한
- JDBC
- Exception
- Android
- Greedy
- java
- 스프링 핵심 기능
- 스프링 핵심 원리
- Proxy
- transaction
- 알고리즘
- 인프런
- 스프링
Archives
- Today
- Total
개발자되기 프로젝트
프록시 본문
1. Member를 조회할 때 Team도 함께 조회해아 하나..??
2. 프록시 기초
- JPA는 em.find()외에도 em.getReference()를 제공
- em.find() vs em.getReference()
- en.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
- em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회.
- DB에 쿼리가 안나가는데 객체가 조회가 됨!
- Proxy는 진짜 객체와 껍데이는 똑같은데, 안이 텅 비었음.
- 내부에 target이 있는데 진짜 레퍼런스를 가르킴.
- em.getReference() 하는 시점에 select SQL 안나감.
Member findMember = em.getReference(Member.class, member.getId());
- 그런데 em.getReference()로 가져온 값이 실제 사용되는 시점에는 쿼리가 날라간다.
- get을 통해 값을 가져오는 시점.
- 특히 userName은 DB에 값이 있기 때문에 DB에 쿼리를 날려서 값을 가져옴.
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember.getUserName() = " + findMember.getUserName());
System.out.println("findMember.getId() = " + findMember.getId());
Hibernate:
select
member0_.MEMBER_ID as member_i1_4_0_,
member0_.createdBy as createdb2_4_0_,
member0_.createdDate as createdd3_4_0_,
member0_.lastModifiedBy as lastmodi4_4_0_,
member0_.lastModifiedDate as lastmodi5_4_0_,
member0_.modifiedBy as modified6_4_0_,
member0_.LOCKIER_ID as lockier_8_4_0_,
member0_.TEAM_ID as team_id9_4_0_,
member0_.USERNAME as username7_4_0_,
locker1_.id as id1_3_1_,
locker1_.name as name2_3_1_,
team2_.TEAM_ID as team_id1_8_2_,
team2_.createdBy as createdb2_8_2_,
team2_.createdDate as createdd3_8_2_,
team2_.lastModifiedBy as lastmodi4_8_2_,
team2_.lastModifiedDate as lastmodi5_8_2_,
team2_.modifiedBy as modified6_8_2_,
team2_.name as name7_8_2_
from
Member member0_
left outer join
Locker locker1_
on member0_.LOCKIER_ID=locker1_.id
left outer join
Team team2_
on member0_.TEAM_ID=team2_.TEAM_ID
where
member0_.MEMBER_ID=?
findMember.getUserName() = hello
findMember.getId() = 1
- 그러면 em.gerReference()로 가져온 객체의 정체는 뭐냐?
- class가 Member가 아니라 Hibernate에서 만든 Proxy 객체이다.
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember = " + findMember.getClass());
findMember = class hellojpa.Member$HibernateProxy$5MkyQjYb
3. 프록시란
- 실제 클래스를 상속 받아서 만들어짐.
- 실제 클래스와 겉 모양이 같음
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면됨 ㅋㅋ
- 프록시 객체는 실제 객체의 참고(target)을 보관
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 매서드 호출함.
4. 프록시 객체의 초기화
Member member = em.getReference(Member.class, “id1”);
member.getName()
- target이 비어있으면 영속성 컨텍스트에 초기화 요청함.
- 영속성 컨텍스트는 DB에 조회해서 엔티티 생성.
- target과 진짜 객체가 연결됨
- 즉 영속성 컨텍스트를 통해 초기화됨.
5. 프록시의 특징
- 프록시 객체는 처음 사용할 때 한 번 만 초기화됨.
- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님,
초기화되면 프록시 객체를 통해서~ 실제 엔티티에 접근이 가능함. - 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야해.
- 타입이 달라, 따라서 ==비교하면 실패하니, instance of를 사용해야해)
- hibernate가 실제 객체를 상속받아 proxy객체로 만듦.
- class hellojpa.Member$HibernateProxy$5MkyQjYb 이런식임. - 영속성 컨텍스트에 찾는 엔티티가 이미 있으면, em.getReference()를 호출해도 실제 엔티티 반환.
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 = " + findMember.getClass()); Member reference = em.getReference(Member.class, member.getId()); System.out.println("reference.getClass() = " + reference.getClass()); tx.commit();
findMember = class hellojpa.Member reference.getClass() = class hellojpa.Member
- 왜..? 같지..?
- 이미 member를 1차 캐시에 올려놨는데 proxy로 가져오는 이점이 없음 그냥 쓰면 됨.
- JPA에서는 한 Transaction안에서 "=="비교시 PK가 같거나 영속성 컨텍스트에서 가져온 객체를 비교 시
항상 TRUE 여야 함.
- getReference를 두 번 호출하면..? 호출 된 두 proxy는 같을까?
같다.Member member = new Member(); member.setUserName("hello"); em.persist(member); em.flush(); em.clear(); Member findMember = em.getReference(Member.class, member.getId()); System.out.println("findMember = " + findMember.getClass()); Member reference = em.getReference(Member.class, member.getId()); System.out.println("reference.getClass() = " + reference.getClass()); System.out.println("a == a : " + (findMember == reference)); tx.commit();
findMember = class hellojpa.Member$HibernateProxy$1o8yn3SK reference.getClass() = class hellojpa.Member$HibernateProxy$1o8yn3SK a == a : true
- 그런데, getReference()하고 .getUserName()을 통해 초기화 된 상태에서
em.find()를 실행하면..?
- JPA는 동일 트랜잭션 내에서 ==비교시 PK가 같으면 항상 TRUE를 보장해야함.
- 그렇다면 처음에 조회한 m1은 proxy 가 분명한데, 나중에 조회한 m2는..?
Member member = new Member();
member.setUserName("hello");
em.persist(member);
em.flush();
em.clear();
Member m1 = em.getReference(Member.class, member.getId());
System.out.println("m1 = " + m1.getClass()); //프록시
m1.getUserName(); // proxy 초기화됨.
Member m2 = em.find(Member.class, member.getId());
System.out.println("m2.getClass() = " + m2.getClass()); //실제 객체?
System.out.println("a == a : " + (m1 == m2)); //PK가 같으니 JPA는 ==비교시 항상 TRUE를 보장해야함.
tx.commit();
- 둘 다 같은 프록시가 반환된다.
- 즉 한번 프록시가 반환되면 이후에 em.find를 해도 프록시를 반환한다.
-
m1 = class hellojpa.Member$HibernateProxy$JsBGTOC7 m2.getClass() = class hellojpa.Member$HibernateProxy$JsBGTOC7 a == a : true
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때 프록시를 초기화하면 문제 발생.
하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림
- em.detatch(), em.close, em.clear() 만나는 순간 proxy는 영속성 컨텍스트 도움 못받음.
Member refMember = em.getReference(Member.class, member.getId());
System.out.println("refMember = " + refMember.getClass()); //프록시
//준영속 상태로 바꿔버리면?
em.detach(refMember);
//초기화 영속성 컨텍스트를 통해 진행되어야 하나,
//준영속 상태는 영속성 컨텍스트에서 관리하지 않기 때문에
//초기화 불가능.
refMember.getUserName();
- 영속성 컨텍스트를 clear해도 proxy 초기화 안됨.
- 영속성 컨텍스트를 끈 건 아니지만.. 완전히 지우고 새로 만든 것 과 같음.
Member refMember = em.getReference(Member.class, member.getId());
System.out.println("refMember = " + refMember.getClass()); //프록시
em.clear();
refMember.getUserName();
org.hibernate.LazyInitializationException: could not initialize proxy [hellojpa.Member#1] - no Session
- no Session : 결국 영속성 컨텍스트가 없다, 영속성 컨텍스트에서 관리 안된다느말.
org.hibernate.LazyInitializationException: could not initialize proxy [hellojpa.Member#1] - no Session
6. 프록시 확인
- 프록시 인스턴트의 초기화 여부 확인
- emf.getPersistenceUnitUtil.isLoaded(Object entity)
-
Member refMember = em.getReference(Member.class, member.getId()); System.out.println("refMember = " + refMember.getClass()); //프록시 //초기화 안한상태. System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
-
isLoaded = false
-
Member refMember = em.getReference(Member.class, member.getId()); System.out.println("refMember = " + refMember.getClass()); //프록시 refMember.getUserName(); //초기화 상태 System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
-
isLoaded = true
- 프록시 클래스 확인 방법
- entity.getClass().getName() 출력(..javasist.. or HibernateProxy…)
- 프록시 강제 초기화
- org.hibernate.Hibernate.initialize(entity);
- Hibernate.initialize(entity);
- 참고: JPA 표준은 강제 초기화 없음
- 강제 호출: member.getName()
* JPA는 동일 transaction 내에서 동일 PK로 조회한 객체에 대하여 == 비교시 항상 true 보장.
동일정 보장함.
7. GitHub : 210811 Proxy
'인프런 > [인프런] 자바ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
영속성 전이(CASCADE),고아 객체 (0) | 2021.08.12 |
---|---|
즉시 로딩과 지연 로딩. (0) | 2021.08.12 |
[예제] 상속관계 매핑 (0) | 2021.08.11 |
@MappedSuperclass (0) | 2021.08.11 |
상속관계 (0) | 2021.08.11 |
Comments