diff --git "a/Session 08 - \355\224\204\353\241\235\354\213\234\354\231\200 \354\227\260\352\264\200\352\264\200\352\263\204 \354\240\225\353\246\254/Chapter 01 - \355\224\204\353\241\235\354\213\234/Note.md" "b/Session 08 - \355\224\204\353\241\235\354\213\234\354\231\200 \354\227\260\352\264\200\352\264\200\352\263\204 \354\240\225\353\246\254/Chapter 01 - \355\224\204\353\241\235\354\213\234/Note.md" index 0b06e71..5e73556 100644 --- "a/Session 08 - \355\224\204\353\241\235\354\213\234\354\231\200 \354\227\260\352\264\200\352\264\200\352\263\204 \354\240\225\353\246\254/Chapter 01 - \355\224\204\353\241\235\354\213\234/Note.md" +++ "b/Session 08 - \355\224\204\353\241\235\354\213\234\354\231\200 \354\227\260\352\264\200\352\264\200\352\263\204 \354\240\225\353\246\254/Chapter 01 - \355\224\204\353\241\235\354\213\234/Note.md" @@ -2,3 +2,206 @@ > 작성자: @luke0408 ## 목차 + +- [프록시](#프록시) + - [목차](#목차) + - [Member를 조회할 때 Team도 함께 조회해야 하는가?](#member를-조회할-때-team도-함께-조회해야-하는가) + - [프록시 기초](#프록시-기초) + - [프록시 조회](#프록시-조회) + - [프록시의 구조](#프록시의-구조) + - [프록시 위임](#프록시-위임) + - [프록시 객체의 초기화](#프록시-객체의-초기화) + - [초기화 과정](#초기화-과정) + - [프록시 초기화의 특징](#프록시-초기화의-특징) + - [프록시 확인](#프록시-확인) + - [프록시 인스턴스의 초기화 여부 확인](#프록시-인스턴스의-초기화-여부-확인) + - [프록시 클래스 확인 방법](#프록시-클래스-확인-방법) + - [프록시 강제 초기화](#프록시-강제-초기화) + +## Member를 조회할 때 Team도 함께 조회해야 하는가? + +[프록시를 사용하지 않은 경우] +> Member만 조회하고 싶은데 Team도 함께 조회하는 문제가 발생한다. + +- Member와 Team을 함께 출력 + +```java +public void printMemberAndTeam(String memberId) { + Member member = em.find(Member.class, memberId); + Team team = member.getTeam(); + System.out.println("member = " + member.getUsername()); + System.out.println("team = " + team.getName()); +} +``` + +- Member만 출력 + +```java +public void printMember(String memberId) { + Member member = em.find(Member.class, memberId); + Team team = member.getTeam(); + System.out.println("member = " + member.getUsername()); +} +``` + +## 프록시 기초 + +### 프록시 조회 + +**em.find()** +- 데이터베이스를 통해서 실제 엔티티 객체 조회 +- 즉, `em.find()를 호출할 때` 데이터베이스를 통해서 실제 엔티티 객체를 조회한다. + +```java +Member member = em.find(Member.class, memberId); // 이 시점에 쿼리를 날린다. +System.out.println("member.id = " + member.getId()); +System.out.println("member.username = " + member.getUsername()); +``` + +**em.getReference()** +- 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회 +- 즉, `em.getReference()를 호출할 때` 데이터베이스 조회를 하는 것이 아닌, 데이터를 사용할 때 조회한다. + +```java +Member member = em.getReference(Member.class, memberId); // 이 시점에는 쿼리를 날리지 않는다. +System.out.println("member.id = " + member.getId()); // 이 시점에 쿼리를 날린다. +System.out.println("member.username = " + member.getUsername()); // 위에서 가져온 데이터를 사용한다. +``` + +### 프록시의 구조 + +
+Proxy 1-1 +

+ +- 실제 클래스를 상속 받아서 만들어진다. +- 실제 클래스와 겉 모양이 같다. +- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다. (이론상) + +### 프록시 위임 + +
+Proxy 1-2 +
+ +- 프록시 객체는 실제 객체의 참조(target)를 보관한다. +- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출 + +## 프록시 객체의 초기화 +> 프록시 객체 초기화란 프록시 객체가 실제 사용될 때 데이터베이스를 조회해서 `실제 엔티티 객체를 생성`하는 것 + +### 초기화 과정 + +```java +Member member = em.getReference(Member.class, memberId); // 프록시 객체를 조회한다. +member.getName(); // 프록시 객체를 초기화한다. +``` + +
+image +

+ +[과정 설명] + +1. 프록시 객체에 member.getName()을 호출해서 실제 데이터를 조회한다. +2. 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트 실제 엔티티 생성을 요청한다. +3. 영속성 컨텍스트는 데이터베이스를 조회해서 실제 엔티티 객체를 생성한다. +4. 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버변수에 보관한다. +5. 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과를 반환한다. + +### 프록시 초기화의 특징 + +- 프록시 객체는 처음 사용할 때 한 번만 초기화 된다. + +- 프록시 객체를 초기화 할 때, `프록시 객체가 실제 엔티티로 바뀌는 것은 아님` + - 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능 + - 1번과 2번 출력이 같은 클래스인 것을 확인할 수 있다. + + ```java + Member member = em.getReference(Member.class, memberId); // 프록시 객체를 조회한다. + System.out.println("before member class = " + member.getClass()); // 1번 + System.out.println("member.id = " + member.getId()); + System.out.println("after member class = " + member.getClass()); // 2번 + ``` + +- 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 + - (== 비교 실패, 대신 instance of 사용) + +- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환 + - 왜? 같은 Transaction에서 a와 a는 같은 객체여야하기 때문에 + + ```java + Member member = new Member(); + member.setName("member"); + em.persist(member); + + em.flush(); + em.clear(); + + //----------------- + //영속성 컨텍스트에 찾는 엔티티가 있는 경우 엔티티를 반환한다. + Member findMember = em.find(Member.class, member.getId()); + System.out.println(findMember.getClass()); //실제 엔티티 반환 + + Member reference = em.getReference(Member.class, member.getId()); + System.out.println(reference.getClass()); //실제 엔티티 반환 + + System.out.println("a == a : " + (findMember == reference)); //true + + em.clear(); + + //----------------- + //프록시가 이미 있는 경우 프록시를 반환한다. + Member reference2 = em.getReference(Member.class, member.getId()); + System.out.println(reference2.getClass()); //프록시 반환 + + Member findMember2 = em.find(Member.class, member.getId()); + System.out.println(findMember2.getClass()); //프록시 반환 + + System.out.println("a == a : " + (reference2 == findMember2)); //true + ``` + +- 영속성 컨텍스트의 도움을 받을 수 없는 `준영속 상태일 때, 프록시를 초기화`하면 문제 발생 + - 하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림 + - no Session 에러 발생 (= 영속성 컨텍스트에 없음) + + ```java + Member member = em.getReference(Member.class, memberId); // 프록시 객체를 조회한다. + em.detach(member); // 영속성 컨텍스트에서 분리한다. + member.getName(); // 프록시 초기화 불가능 -> 예외 발생 + ``` + +## 프록시 확인 + +### 프록시 인스턴스의 초기화 여부 확인 + +- emf.getPersistenceUnitUtil().isLoaded(Object entity) + - JPA 표준은 emf.getPersistenceUnitUtil().isLoaded(Object entity)로 확인 + - Hibernate는 Hibernate.isInitialized(Object entity)로 확인 + +```java +Member member = em.getReference(Member.class, memberId); // 프록시 객체를 조회한다. +System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(member)); //false +member.getName(); // 프록시 초기화 +System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(member)); //true +``` + +### 프록시 클래스 확인 방법 + +- entity.getClass().getName()으로 확인 + +```java +Member member = em.getReference(Member.class, memberId); // 프록시 객체를 조회한다. +System.out.println("member.getClass().getName() = " + member.getClass().getName()); +``` + +### 프록시 강제 초기화 + +- org.hibernate.Hibernate.initialize(entity) + - JPA 표준은 강제 초기화 없음 + - Hibernate는 Hibernate.initialize(entity)로 강제 초기화 가능 + +```java +Member member = em.getReference(Member.class, memberId); // 프록시 객체를 조회한다. +Hibernate.initialize(member); // 강제 초기화 +```