Spring Framework/[인프런] Java ORM 표준 프로그래밍 - JPA

Chapter 08. 프록시와 연관 관계 관리

계란💕 2022. 9. 7. 13:55

8.1 프록시(proxy)

 

프록시(proxy)

  • em.find(): 데이터베이스를 통해 실제 엔티티 객체를 조회한다.
  • em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회한다.
    • 하이버네이트가 라이브러리를 사용해서 가짜 엔티티를 준다.
    • DB에 쿼리가 안 날아가는데 객체가 조회되는 것이다.
    • ex) Member를 조회할 때 Team도 조회해야할까?
      • 상황마다 다르다
      • 어떤 프로젝트의 경우, 멤버만 필요 어떤 프로젝트의 경우는 멤버와 팀 둘다 필요할 수 있다.
        • 이를 "프록시"로 해결 가능하다.

 

 

프록시의 특징

  • 실제 클래스를 상속 받았기 때문에 실제 Entity와 겉 모양이 같다.
  • 사용하는 입장에서는 진짜인지 프록시 객체인지 구분하지 않고 사용하도록 한다.

  • 프록시 객체는 실제 객체의 참조(target)를 보관한다.
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체 Entity의 메서드를 호출한다.

 

 

프록시 객체의 초기화 과정

  • 1) getName()을 호출 =>그런데 MemberProxy에 값이 없다.
  • 2) Member target에 값이 없으면 JPA가 영속성 컨텍스트에 값을 요청한다. 
  • 3) ~ 4) 그럼 영속성 컨텍스트가 DB를 조회한다. 실제 엔티티 객체를 생성해서 준다.
  • 5) 그러고나서 target 에 진짜 객체를 연결해준다.
  • 프록시 객체는 실제 엔티티가 생성되어 있지 않은 경우, 실제 엔티티 생성을 요청하는데 이를 프록시 초기화라고 한다.

 

 

프록시 특징

  • 프록시 객체는 처음 딱 한 번만 초기화한다.
  • 프록시 객체를 초기화할 때, 프록시 객체가 실제 객체로 바뀌는 것은 아니다. 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능하다.
  • 프록시 객체는 원본 엔티티를 상속 받는다. 따라서, 타입 체크를 주의해야한다. ("=="가 아닌 "instance of"를 사용한다.) "=="는 상속한 경우 false를 반환한다. 타입 비교할 때 실제로 넘어올지 프록시로 넘어올지 모르기 때문이다.
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 생긴다.
    • LazyInitailizationException 발생

 

 

  Ex) em.find(): 데이터베이스에서 실제 엔티티 객체를 조회

<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        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();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }

    private static void printMember(Member member){
        System.out.println("member = " + member.getUserName());
    }

    private static void printMemberAndTeam(Member member) {

        String userName = member.getUserName();
        System.out.println("userName = " + userName);
        Team  team = member.getTeam();
        System.out.println("team = " + team.getName());
    }
}

  Note) 실행 결과 - find()를 이용했기 때문에 DB에 접근하기 위해 SELECT 쿼리를 날린다.

 

 

  Ex) em.getRefence() 

  • println() 없는 경우
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            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.id = " + findMember.getId());
//            System.out.println("findMember.userName = " + findMember.getUserName());
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }

    private static void printMember(Member member){
        System.out.println("member = " + member.getUserName());
    }

    private static void printMemberAndTeam(Member member) {

        String userName = member.getUserName();
        System.out.println("userName = " + userName);
        Team  team = member.getTeam();
        System.out.println("team = " + team.getName());
    }
}

  Note) 실행 결과 - CREATE는 있지만 SELECT 쿼리는 없음

  • Member 인스턴스 findMember를 만들기 위해 em.getReference() 하는 시점에는 쿼리가 안 날아간다.
  • 그런데,  getReference()의 결과인 findMember를 실제 사용하는 시점이 되면 쿼리가 실행된다.

 

  • println() 있는 경우 
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            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.id = " + findMember.getId());
            System.out.println("findMember.userName = " + findMember.getUserName());
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }

    private static void printMember(Member member){
        System.out.println("member = " + member.getUserName());
    }

    private static void printMemberAndTeam(Member member) {

        String userName = member.getUserName();
        System.out.println("userName = " + userName);
        Team  team = member.getTeam();
        System.out.println("team = " + team.getName());
    }
}

  Note) 실행 결과

  • SELECT 쿼리
  • em.getId()를 실행할 때는 쿼리가 실행되지 않고 em.getUserName()을 실행할 때 쿼리가 실행된다.
    • getReference() 할 때 파라미터로 id를 넣었기 때문이다.
    • 그래서 쿼리를 날리지 않더라도 1차 캐시에서 바로 Id를 가져올 수 있는 것이다.
    • getUserName() 하는 시점에 JPA가 쿼리를 날려서 userName 값을 채운 다음에 출력하는 것이다.

 

  • 아래 코드를 추가한다.
System.out.println("findMember = " + findMember.getClass());

 

  • 결과를 보면 클래스 이름이 Member가 아니다.
    • 이는 하이버네이트가 강제로 만든 Member의 가짜 클래스인 프록시 클래스이다.

 

 

  Ex) 프록시 초기화 - getClass(), getUserName() 

<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            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());
            System.out.println("findMember.userName = " + findMember.getUserName());
            System.out.println("findMember.userName = " + findMember.getUserName());
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }

    private static void printMember(Member member){
        System.out.println("member = " + member.getUserName());
    }

    private static void printMemberAndTeam(Member member) {

        String userName = member.getUserName();
        System.out.println("userName = " + userName);
        Team  team = member.getTeam();
        System.out.println("team = " + team.getName());
    }
}

  Note) 실행 결과

  • getUserName()을 처음 할 때는 프록시에 값이 없으니까 초기화 요청을 하고 (데이터베이스에 SELECT 쿼리를 날리고 프록시 객체 생성)
  • 두 번째 출력에서는 target에 값이 있으니까(이미 초기화된 프록시니까) SELECT 없이 바로 UserName을 출력한다.

 

 

  Ex)  프록시 객체를 초기화할 때, 프록시 객체가 실제 객체로 바뀌는 것은 아니다. 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능하다.

<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            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("before findMember = " + findMember.getClass());
            System.out.println("findMember.userName = " + findMember.getUserName());
            System.out.println("after findMember = " + findMember.getClass());
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }

    private static void printMember(Member member){
        System.out.println("member = " + member.getUserName());
    }

    private static void printMemberAndTeam(Member member) {

        String userName = member.getUserName();
        System.out.println("userName = " + userName);
        Team  team = member.getTeam();
        System.out.println("team = " + team.getName());
    }
}

  Note) 실행 결과

  • before와 after의 프록시 객체가 같다.

 

 

  Ex) instance of를 사용해야하는 이유 

  • find와 "=="으로 비교
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        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.find(Member.class, member2.getId());
            System.out.println("m1 == m2 " + (m1.getClass() == m2.getClass()));
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }

    private static void printMember(Member member){
        System.out.println("member = " + member.getUserName());
    }

    private static void printMemberAndTeam(Member member) {

        String userName = member.getUserName();
        System.out.println("userName = " + userName);
        Team  team = member.getTeam();
        System.out.println("team = " + team.getName());
    }
}

  Note) 실행 결과

 

 

  • m1은 find로 가져오고 m2만 getReference()로 가져오면?
    • "=="은 타입까지 정확하게 비교해야하기 때문에 false가 나온다.
    • m2는 프록시 객체(Member를 상속 받은 하위 클래스)이므로 false 출력

 

  • 따라서 다음과 같이 instanceof로 비교하도록 한다.
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        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();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
    private static void logic(Member member1, Member member2){
        System.out.println("m1 == m2 : " + (member1 instanceof Member));
        System.out.println("m1 == m2 : " + (member2 instanceof Member));
    }
}

  Note) 실행 결과

 

 

  Ex) 엔티티 조회 후, 프록시 조회

<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        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();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • 왜 둘이 같게 나올까?
    • 이미 영속성 컨텍스트에 멤버가 있는데 굳이 프록시를 가져올 필요없다.
    • JPA 에서는 하나의 트랜잭션 안에서 같다는 걸 반드시 보장한다.
    • "==" 로 비교할 때 true로 반환하기 위해서 이미 영속성 컨텍스트에 엔티티가 있으면 getReference()를 하더라도 실제 객체 entity를 반환하도록 한다.
  • 아래 코드 추가
    • 앞서 getClass()로 조회해서 비교한 결과 false와 다르다.
System.out.println("a == a: " + (m1 == reference));

 

 

  Ex) 둘다 프록시로 가져와서 비교

<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        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();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • 위의 예제와 마찬가지로 true가 반환되어야만 한다.
  • 그래서 같은 프록시를 가져온다.

 

 

  Ex) 프록시 조회 후, 엔티티 조회

<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        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("refMember == findMember : " + (refMember == findMember));
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • true를 맟추기 위해서 둘다 프록시로 반환된다.
  • 맨 처음에 프록시를 조회하면 나중에 find()로 실제 엔티티를 조회할 때도 프록시가 조회된다.

 

 

  Ex)  준영속 상태일 때, 프록시를 초기하면? LazyInitializationException

  • em.detach(refMember)를 추가해서 영속성 컨텍스트에서 refMember를 분리한다.
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        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());  // 프록시
            em.detach(refMember);   // 더 이상 영속성 컨텍스트에서 관리하지 않도록 끄집어 낸다.
            System.out.println("refMember = " + refMember.getUserName());
            tx.commit();
        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • 프록시는 출력된다.
  • detach() 하면 더이상 영속성 컨텍스트에서  관리하지 않는다.
  • getUserName()을 하면 영속성 컨텍스트에서 실제 데이터를 불러와서 초기화를 해야한다.
  • 근데 이전에 detach() 했기 때문에(준영속 상태) 초기화 불가능 - could not initialize proxy [hellojpa.Member#1] - no Session
    •  no Session: 영속성 컨텍스트에 없다는 의미이다.
  • em.close() / em.clear() 를 해도 똑같은 결과가 나온다. 닫으면 안에 있는 내용이 다 날라간다.
  • detach / close / clear를 만나는 순간 ref는 더이상 영속성 컨텍스트의 도움을 받지 못한다.

 

 

프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인: PersistenceUnitUtil.isLoaded(Object entity)
  • 프록시 클래스 확인 방법: entity.getClass().getName() 출력 (..javasist .. or HibernateProxy...)
  • 프록시 강제 초기화: org.hibernate.Hibernate.initialize(entity); - 하이버네이트 제공
    • JPA 표준은 강제 초기화가 없다.

 

 

  Ex) 프록시 인스턴스의 초기화 여부 확인 - PersistenceUnitUtil .isLoaded(Object entity)

<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        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());  // 프록시
            System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
            tx.commit();
        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과 - 초기화 되지 않은 상태이니까 false

 

  • 다음과 같이 코드를 추가해서 초기화하면?
  • getUserName() 추가
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        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());  // 프록시
            refMember.getUserName();
            System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
            tx.commit();
        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과 

  • SELECT 쿼리가 실행되면서 프록시 초기화가 되고 true가 출력된다.

 

 

  Ex) 프록시 클래스 확인 방법 - getClass() 출력 

<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        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());  // 프록시
            refMember.getUserName();
            tx.commit();
        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

 

 

  Ex) 강제 초기화 - Hibernate.initialize(member)

<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.hibernate.Hibernate;

public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

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

        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과 - SELECT 쿼리가 나가면서 강제로 초기화된다.

 

 

 

8.2 즉시 로딩과 지연 로딩

 

지연 로딩(LAZY loading)

  • fetch 타입을 LAZY로 하면 연관된 것을 프록시로 가져온다.
  • member1을 로딩할 때 team 인스턴스는 지연 로딩으로 세팅되어 있으니까 프록시로 가져온다.

 

 

지연 로딩(LAZY loading) 사용해서 프록시로 조회

지연 로딩 LAZY를 사용해서 프록시로 조회

  • 멤버를 가져왔을 때, 지연로딩 세팅이 되어 있으면 가짜 프록시 객체를 받아 놓는다.
  • 그러고나서 실제 team을 사용하는 시점이 되어야 DB가 초기화되고 쿼리가 나간다.
    • 팀을 가져올 때가 아니라 "실제 사용할 때" 초기화

 

 

  Ex) 지연 로딩(LAZY loading)

  • Member클래스의 Team객체에 다음과 같이 LAZY 추가
  • 아래과 같이 하면 프록시 객체로 조회한다.
  • Member 클래스만 DB에서 조회한다.
<hide/>
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn
private Team team;  // 연관 관계의 주인
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.hibernate.Hibernate;

public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            Member member1 = new Member();
            member1.setUserName("member1");
            em.persist(member1);
            em.flush();
            em.clear();

            Member m = em.find(Member.class, member1.getId());
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과 - Member 클래스만 조회된다.

 

  • Team 추가
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.hibernate.Hibernate;

public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            Team team = new Team();
            team.setName("teamA");
            em.persist(team);

            Member member1 = new Member();
            member1.setUserName("member1");
            member1.setTeam(team);
            em.persist(member1);
            em.flush();
            em.clear();

            Member m = em.find(Member.class, member1.getId());
            System.out.println("m = " + m.getTeam().getClass());
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • 멤버 가져올 때는 멤버만 가져오고 팀을getClass()로 조회하니까 프록시로 가져온다.
  • Team에 대한 쿼리는 실행되지 않는다.

 

 

  • 그러면 팀 내용을 건드리면(setTeam()) 그 시점에 쿼리가 나갈까?
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.hibernate.Hibernate;

public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            Team team = new Team();
            team.setName("teamA");
            em.persist(team);

            Member member1 = new Member();
            member1.setUserName("member1");
            member1.setTeam(team);
            em.persist(member1);
            em.flush();
            em.clear();

            Member m = em.find(Member.class, member1.getId());
            System.out.println("m = " + m.getTeam().getClass());
            System.out.println("==========");
            m.getTeam().getName();
            System.out.println("==========");
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • m.getTeam().getName() 하는 시점에 프록시 객체가 초기화 되면서 DB에서 값을 가져온다.
  • 그래서 지연로딩으로 세팅하면 연관된 내용을 프록시로 가져온다.

 

 

즉시 로딩(EAGER loading)

  • 즉시 로딩은 member를 쓸 때마다 Team을 join해서 같이 가져온다. team이 항상 필요한 경우에 사용한다.
  • Member 조회 시 항상 Team도 함께 조회한다.

 

  • JPA 구현체는 가능하면 조인을 사용해서 SQL 한 번에 함께 조회한다.
  • 실무에서는 즉시 로딩을 쓰지 않는다.

 

 

프록시와 즉시 로딩 주의 사항

  • 실무에서는 가급적 지연 로딩만 사용한다.
  • Why? 
    • 즉시 로딩을 적용하면 예상치 못한 SQL이 발생한다.
  • 즉시로딩은 JPQL에서 N + 1 문제를 일으킨다.
    • 최초 쿼리가 하나 실행되고 그에 따라서 N개의 쿼리가 추가적으로 나가기 때문에 N + 1이라고 한다.
    • N + 1 문제 해결 방법
      • 모든 연관 관계를 지연 로딩으로 세팅
      • 페치 조인(fetch JOIN) - 관련된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능이다. 
  • @ManyToOne, @OneToOne 은 기본이 즉시 로딩이니까 이 부분을 "LAZY"로 바꿔야 한다.
  • @OneToMany, @ManyToMany는 기본이 지연 로딩이다.

 

 

지연 로딩 활용

  • Member와 Team을 자주 함께 사용 => 즉시 로딩
  • Member와 Order는 가끔 사용 => 지연 로딩
  • Order와 Product는 자주 함께 사용 => 즉시 로딩

 

 

  • 위 그림에서 eager인 경우 find(team) 할 때 한방 쿼리가 나간다. (멤버와 팀)
  • 리스트는 lazy이기 때문에 프록시로 들어온다.

 

  • 그런데 만약 프록시를 터치하면?
    • 상품은 eager 설정된 상태
    • 그럼  list와 상품 A를 함께 가져온다.

 

 

지연 로딩 활용 실무와 권장 사항

  • 모든 연관 관계에 지연 로딩을 사용하라.
  • 실무에서 즉시 로딩을 사용하지 말아야한다.
  • JPQL  fetch 조인이나, 엔티티 그래프 기능을 사용해야한다.

 

 

  Ex) 즉시 로딩(EAGER)을 사용해서 함께 조회

  • Member클래스의 팀 객체를 수정한다.
<hide/>
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn
private Team team;  // 연관 관계의 주인
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.hibernate.Hibernate;

public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            Team team = new Team();
            team.setName("teamA");
            em.persist(team);

            Member member1 = new Member();
            member1.setUserName("member1");
            member1.setTeam(team);
            em.persist(member1);
            em.flush();
            em.clear();

            Member m = em.find(Member.class, member1.getId());
            System.out.println("m = " + m.getTeam().getClass());
            System.out.println("==========");
            m.getTeam().getName();
            System.out.println("==========");
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • 멤버와 팀을 조인해서 한 번에 다 가져온다.
  • 그래서 팀의 클래스 타입을 출력하면 프록시가 아닌 실제 Entity를 가져온다.

 

 

  Ex) 즉시 로딩은 JPQL에서 N + 1 문제를 일으킨다.

  • EAGER로 세팅한 상태

  Note) 실행 결과

  • 왜 쿼리가 두 번 나갈까?
  • em.find() 는 PK를 찍어서 가져오기 때문에 JPA가 내부적으로 최적화 가능하다.
  • 그런데, JPQL은  createQuery()안의 내용이  SQL로 그대로 번역된다.

 

  • 순서 
    • createQuery()안에 있는 내용을 DB에 쿼리로 날린다.
    • 멤버를 가져온다.
    • 그런데, 멤버 클래스의 팀을 보니 즉시 로딩으로 되어 있는 상태 (즉시 로딩은 값이 무조건 다 들어가 있어야한다.) 
    • 멤버의 개수가 10개면 eager를 가져오기위해서 쿼리가 별도로 나간다. (EAGER는 반환하는 시점에 값이 다 들어가 있어야한다.) 

 

 

  Ex) 즉시 로딩의 N + 1 문제

  • 최초 쿼리가 하나 나가고 그에 따라서 N개의 쿼리가 추가적으로 나가기 때문에 N + 1이라고 한다.
<hide/>
package hellojpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            Team teamA = new Team();
            teamA.setName("teamA");
            em.persist(teamA);
            Team teamB = new Team();
            teamB.setName("teamB");
            em.persist(teamB);
            
            Member member1 = new Member();
            member1.setUserName("member1");
            member1.setTeam(teamA);
            em.persist(member1);
            Member member2 = new Member();
            member2.setUserName("member2");
            member2.setTeam(teamB);
            em.persist(member2);

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

//            Member m = em.find(Member.class, member1.getId());
            List<Member> members = em.createQuery(" SELECT m FROM Member m ", Member.class)
                                                                        .getResultList();
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

 

  Note) 실행 결과

  • member1, member2가 다른 팀이라서 영속성 컨텍스트에 없다. SELECT 쿼리를 두 번 실행해서 각각 팀을 가져와야한다.

 

  • 즉시 로딩이 아닌 LAZY로 바꾸면?

  Note) 실행 결과

  • 다음과 같이 쿼리가 하나만 나간다. 
  •  팀은 proxy로 박혀있기 때문에 팀에 대한 쿼리는 나오지 않는다.

 

 

  Ex) N +1 해결 방법 - fetch JOIN

  • 멤버는 LAZY 로 설정된 상태
<hide/>
package hellojpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            Team teamA = new Team();
            teamA.setName("teamA");
            em.persist(teamA);
            Team teamB = new Team();
            teamB.setName("teamB");
            em.persist(teamB);

            Member member1 = new Member();
            member1.setUserName("member1");
            member1.setTeam(teamA);
            em.persist(member1);
            Member member2 = new Member();
            member2.setUserName("member2");
            member2.setTeam(teamB);
            em.persist(member2);

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

//            Member m = em.find(Member.class, member1.getId());
            List<Member> members = em.createQuery(" SELECT m FROM Member m JOIN FETCH m.team ", Member.class)
                                                                        .getResultList();
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • 멤버와 팀을 모두 가져온다.
  • fetch JOIN 했기 때문에 팀에 관한 데이터가 이미 채워져서 나온다.

 

 

 

8.3 영속성 전이(CASCADE)와 고아 객체

 

영속성 전이(CASCADE)

  • 특정 엔티티를 영속 상태로 만들 때, 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 영속성 전이를 사용한다.
  • ex) 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장한다.
  • 주의 사항
    • 영속성 전이는 연관 관계를 매핑하는 것과 아무 관련이 없다.
    • 엔티티를 영속화할 때, 연관된 엔티티도 함께 영속화하는 편리함을 제공한다.
  • 영속성 전이의 종류
    • ALL: 모두 적용 - 라이프 사이클을 모두 맞춘다. 상위엔 티티에서 하위 엔티티로 모든 작업을 전파한다.
    • PERSIST: 영속 - 저장할 때만 쓴다. 하위 엔티티까지 영속성을 전달한다.
    • REMOVE: 삭제
    • MERGE: 병합
    • REFRESH: REFRESH
    • DETACH: DETACH
  • 영속성 전이를 쓰는 경우: 하나의 부모가 자식들을 관리하는 경우, 소유자가 하나인 경우   ex)게시판, 첨부파일 경로
    • 쓰면 안되는 경우: 파일을 여러 곳에서 관리하는 경우

 

 

영속성 전이(CASCADE)와 저장

 

 

  Ex) 영속성 전이

  • Parent, Child
<hide/>
package hellojpa;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;

@Entity
class Parent {

    @Id  @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "parent") // child에 있는 Parent 객체의 이름
    private List<Child> childList =  new ArrayList<>();

    // 연관 관계 편의 매서드
    public void addChild(Child child){
        childList.add(child);
        child.setParent(this);
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
<hide/>
package hellojpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
class Child {

    @Id  @GeneratedValue
    private Long id;
    private String name;

    @ManyToOne
    @JoinColumn(name ="parent_id")
    private Parent parent;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Parent getParent() {
        return parent;
    }

    public void setParent(Parent parent) {
        this.parent = parent;
    }
}
  • persist() 세 번 해줘야한다.
<hide/>
package hellojpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            Child child1 = new Child();
            Child child2 = new Child();
            Parent parent = new Parent();
            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);
            em.persist(child1);
            em.persist(child2);
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • parent를 persist() 할 때 child까지 자동으로 persist() 하도록 하려면?
    • 다음과 같이 부모 클래스에 영속성 전이를 설정한다.
    • (cascade = CascadeType.ALL)
<hide/>
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) // child에 있는 Parent 객체의 이름
private List<Child> childList =  new ArrayList<>();

 

  • 아래와 같이 persist(parent) 를 하나만 남기고 지운다.
<hide/>
package hellojpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            Child child1 = new Child();
            Child child2 = new Child();
            Parent parent = new Parent();
            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과 - child에 대해서도 INSERT 쿼리가 자동으로 실행된다.

 

 

고아 객체 - orphanRemoval 

  • 고아 객체 제거: 부모 엔티티와 연관 관계가 끊어진 자식 엔티티를 자동으로 삭제한다.
  • orphanRemoval = true

 

 

고아 객체 - 주의 사항

  • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
  • 참조하는 곳이 하나일 때만 사용한다.
  • 특정 엔티티가 개인 소유일 때만 사용한다.
  • @OneToOne, @OneToMany 만 가능하다.
  •  cf) 부모를 제거하면 자식은 고아가된다. 따라서 기능을 활성화하면 부모를 제거할 때, 자식도 함께 제거된다. 
    • CascadeType.REMOVE 처럼 동작한다.

 

 

영속성 전이 + 고아 객체, 생명 주기

  • CascadeType.ALL + orphanRemoval = true
  • 스스로 생명 주기를 관리하는 엔티티는 em.persist() 로 영속화, em.remove()로 제거한다.
  • 두 옵션을 모두 활성화하면 부모 엔티티를  통해서 자식의 생명 주기를 관리 가능하다.
    • JPA가 parent의 생명 주기를 관리한다. 그런데, child는 parent가 관리한다.
  • 도메인 주도 설계(Domain Driven Design , DDD)의 Aggregate Root 개념을 구현할 때 유용하다.

 

 

  Ex) orphanRemoval

  • parent 클래스
  • orphanRemoval은 컬렉션에 빠진 데이터가 삭제된다.
<hide/>
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) // child에 있는 Parent 객체의 이름
private List<Child> childList =  new ArrayList<>();
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            Child child1 = new Child();
            Child child2 = new Child();
            Parent parent = new Parent();
            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);
            em.flush();
            em.clear();
            Parent findParent = em.find(Parent.class, parent.getId());
            findParent.getChildList().remove(0);    // 인덱스
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과 - DELETE 쿼리가 실행된다.

  • 실행 전 화면

 

  • 실행 후 화면

 

 

  Ex) 부모 객체 제거 - em.remove() 

 

  • 다음과 같이 cascade를 지운다. Parent 클래스
<hide/>
@OneToMany(mappedBy = "parent", orphanRemoval = true) // child에 있는 Parent 객체의 이름
private List<Child> childList =  new ArrayList<>();

 

  • 아래와 같이 em.remove() 를 추가한다.
    • 이전 예제에서는 em.remove()가 아니라 findParent.getChildList().remove(0); 실행해서 하나의 자식만 삭제했다.
<hide/>
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            Child child1 = new Child();
            Child child2 = new Child();
            Parent parent = new Parent();
            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);
            em.persist(child1);
            em.persist(child2);
            em.flush();
            em.clear();

            Parent findParent = em.find(Parent.class, parent.getId());
            em.remove(findParent);  // parent에 있는 리스트도 삭제
            tx.commit();

        }catch (Exception e) {
            tx.rollback();
            e.printStackTrace();
        }finally {
            em.close();
        }
        emf.close();    // 팩토리를 나중에 닫는다.
    }
}

  Note) 실행 결과

  • parent 뿐만 아니라 두 개의 child 모두 지워진다.

 

 

 

8.4 실전 예제 5 - 연관 관계 관리

 

글로벌 페치 전략 설정

  • 모든 연관 관계를 지연 로딩으로 바꾼다.
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩으므로 지연 로딩으로 변경해야한다.

 

 

  Ex) 모든 연관 관계를 지연 로딩으로

  • 카테고리
    • @ManyToOne은 기본 설정이 즉시 로딩이니까 LAZY로 바꿔준다.
<hide/>
@ManyToOne(fetch = FetchType.LAZY)  // 자식 입장에서 부모는 하나
@JoinColumn(name ="PARENT_ID")
private Category parent;  // 상위 카테고리

 

  • delivery 
<hide/>
@OneToOne(mappedBy = "delivery", fetch = FetchType.LAZY)
private Order order;

 

  • Order
<hide/>
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name ="MEMBER_ID")
private Member member;

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name ="DELIVERY_ID")
private Delivery delivery;

 

  • OrderItem
<hide/>
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ORDER_ID")
private Order order;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ITEM_ID")
private Item item;

  Note) 실행 결과 - 정상

 

 

 

  Ex) 영속성 전이 설정

 

  • Order => Delivery  영속성 전이 ALL 설정
    • 주문이 생성되서 배달에 넣는다.
    • 주문이 저장되면 배달도 자동으로 저장되야한다.
<hide/>
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name ="DELIVERY_ID")
private Delivery delivery;

 

  • Order => OrderItem 영속성 전이 ALL 설정
<hide/>
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();

  Note) 실행 결과 - Order를 생성하고나서 order만 persist()해주면 나머지는 자동으로 persist() 된다.

 

 

 

출처 - https://www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com