mojo's Blog

프록시와 연관관계 관리 본문

JPA

프록시와 연관관계 관리

_mojo_ 2022. 8. 12. 21:29
프록시

 

 

Member 를 조회할 때 Team 도 함께 조회해야 할지에 대한 이슈가 있다.

아래의 코드를 보도록 하자.

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

 

두 가지 경우가 있다.

1. 멤버 정보만 필요한 경우가 있을 수 있다.

2. 멤버와 팀 정보 또는 팀 정보만 필요한 경우가 있을 수 있다.

 

즉, 비즈니스 상황에 따라 나뉠 수 있는데 select 쿼리로 멤버 객체를 받아올 경우 팀 객체까지 통합해서

가져오게 될 경우 첫 번째 케이스에서는 불필요한 객체가 된다.

불필요한 객체를 가져옴으로써 시간, 공간적 손실을 얻게 된다.

이러한 문제를 해결하기 위해 지연 로딩과 즉시 로딩 방법이 존재한다.

 

 

※ 프록시 기초

 

- em.find() : 데이터베이스를 통해 실제 엔티티 객체를 조회한다.

- em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체를 조회한다.

 

 

멤버를 저장하고 getReference 메서드를 호출하는 코드를 실행해보자.

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 world");

        em.persist(member);

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

        Member findMember = em.getReference(Member.class, member.getId());
        
        tx.commit();
    } catch(Exception e) {
        tx.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

 

멤버 삽입은 insert 쿼리로 정상적으로 수행된 것을 확인할 수 있다.

그러나 getReference 메서드를 호출하면 select 쿼리가 호출되지 않은 것을 알 수 있다.

즉, find 메서드와 다르다는 것을 알 수 있다. (find 는 select 쿼리 호출 o)

 

이번엔 getReference 메서드로 받아온 멤버의 아이디와 이름을 출력하는 코드를 수행해보자.

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 world");

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

 

신기하게도 id 값은 select 쿼리가 수행되지 않고 바로 가져온 것을 알 수 있다.

member 객체의 id 값을 getReference 했기 때문에 굳이 select 쿼리를 수행하지 않고 바로 출력한 것이다.

name 값은 select 쿼리가 수행되었는데 필요한 시점에서 select 쿼리를 날린 것을 알 수 있다.

 

그렇다면 id 를 가져오기 직전과 직후에 getClass 메서드로 어느 클래스에 속해있는지를 확인해보자.

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 world");

        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 = " + findMember.getClass());

        tx.commit();
    } catch(Exception e) {
        tx.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

 

동일하게 HibernateProxy 관련 클래스임을 알 수 있다.

Hibernate 가 강제로 만든 가짜 클래스인 것이다.

 

 

※ 프록시 특징

 

실제 클래스를 상속 받아서 만들어진다.

실제 클래스와 겉 모양이 같다.

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

 

 

프록시 객체는 실제 객체의 참조를 보관한다.

프록시 객체를 호출하면 프록시 객체는 실제 객체의 메서드를 호출하게 된다.

 

 

※ 프록시 객체의 초기화

 

Member member = em.getReference(Member.class, 1L);
member.getName();

 

getReference 메서드를 호출하여 멤버 클래스를 얻고 해당 멤버의 name 을 가져오는 코드다.

아래 과정과 같이 수행이 된다.

 

 

  1. getName() : Proxy 클래스인 MemberProxy 객체로 getName() 을 호출한다.
  2. 초기화 요청 : 초기 MemberProxy 객체의 target 은 NULL 이다. 따라서 영속성 컨텍스트에게 초기화를 요청한다.
  3. DB 조회 : 이 시점에 select 조회가 일어나게 된다.
  4. 실제 Entity 생성 : 이제 MemberProxy 객체의 target 의 참조할 수 있는 Member 클래스의 객체가 생성된다.
  5. target.getName() : MemberProxy 객체의 target 이 존재하므로 getName 메서드를 호출하여 name 을 가져오게 된다.

 

 

※ 프록시의 특징

 

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

프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것이 아니다. (위에서 확인)

초기화가 완료되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능한 것이다.

프록시 객체는 원본 엔티티를 상속받으며, 타입 체크시 주의해야 한다. (== 비교 실패, instance of 사용)

영속성 컨텍스트에 찾는 엔티티가 이미 있을 경우 em.getReference() 를 호출해도 실제 엔티티가 반환된다.

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

(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터뜨림)

 

 

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

아래 코드와 같이 == 비교를 하는 것이 아닌, instance of 을 사용해서 비교해야 한다.

private static void logic(Member m1, Member m2) {
    System.out.println("[Fail] m1 == m2 : " + (m1 == m2));
    System.out.println("[Success] m1 == m2 : " + ((m1 instanceof Member) && (m2 instanceof  Member)));
}

 

 

영속성 컨텍스트에 찾는 엔티티가 이미 있을 경우 getReference 메서드 호출 시 실제 엔티티가 반환된다고 한다.

그렇다면 영속성 컨텍스트를 초기화하는 메서드를 지워보고 수행해보도록 하자.

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 world");

        em.persist(member);

        Member findMember = em.getReference(Member.class, member.getId());
        System.out.println("findMember = " + findMember.getClass());
        System.out.println("findMember.username = " + findMember.getUsername());

        tx.commit();
    } catch(Exception e) {
        tx.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

 

클래스가 Member 인것으로 Proxy 클래스가 아닌 것을 확인할 수 있다.

그리고 insert 쿼리가 늦게된 이유는 flush 를 안해줬기 때문이다! (즉, commit 때 insert 쿼리 수행)

 

 

이번엔 약간 특별한 케이스인데 코드를 살펴보도록 하자.

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 world");

        em.persist(member);

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

        Member refMember = em.getReference(Member.class, member.getId());
        System.out.println("refMember = " + refMember.getClass());

        Member findMember = em.find(Member.class, member.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();
}

 

신기하게도 find 메서드를 호출했는데 프록시 객체가 나타난 케이스이다.

getReference 메서드로 프록시가 한번 조회가 되면 find 메서드에서 프록시를 반환하게 된다.

따라서 " refMember == findMember " 의 결과가 true 가 된 것이다.

 

 

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

이 케이스는 실무에서도 많이 접하는 문제라고 한다.

아래의 코드를 살펴보도록 하자.

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 world");

        em.persist(member);

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

        Member refMember = em.getReference(Member.class, member.getId());
        System.out.println("refMember = " + refMember.getClass());

        em.detach(refMember);

        System.out.println("refMember.username = " + refMember.getUsername());

        tx.commit();
    } catch(Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        em.close();
    }

    emf.close();
}

 

여기서 주의깊게 봐야할 부분은 em.detach(refMember) 이다.

detach 메서드는 영속성 컨텍스트에서 관리하지 않도록 하는 메서드이다. (clear, close 도 마찬가지)

즉, refMember 는 영속성 컨텍스트에서 추방된 상태라고 생각하면 편할 것 같다.

이때 refMember.getUserName() 을 통해 refMember 는 프록시 객체이므로, 영속성 컨텍스트에게

초기화 요청을 하게 되는데 영속성 컨텍스트에서 관리하지 않기 때문에 에러가 난것이다.

 

 

※ 프록시 확인

 

프록시 인스턴스의 초기화 여부 확인

   - PersistenceUnitUtil.isLoaded(Object entity)

 

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 world");

        em.persist(member);

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

        Member refMember = em.getReference(Member.class, member.getId());
        
        System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
        System.out.println("refMember.username = " + refMember.getUsername());
        System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));

        tx.commit();
    } catch(Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        em.close();
    }

    emf.clo

 

프록시 객체의 초기화 전과 후로 결과를 확인해보면 false, true 로 확인이 가능하다.

 

프록시 클래스 확인 방법

   - entity.getClass().getName() 을 출력

 

프록시 강제 초기화

   - org.hibernate.Hibernate.initialize(entity)

 

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 world");

        em.persist(member);

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

        Member refMember = em.getReference(Member.class, member.getId());
        Hibernate.initialize(refMember);
        System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));

        tx.commit();
    } catch(Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        em.close();
    }

    emf.close();
}

 

강제로 초기화되는 것을 확인할 수 있다.

참고로 JPA 표준은 강제 초기화가 없다고 한다.

 

 

즉시 로딩과 지연 로딩

 

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

 

@Entity
public class Member extends BaseEntity {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    
    ...
}

 

위와 같이 team 을 지연 로딩 LAZY 을 사용해서 프록시로 조회하려고 한다.

아래 코드를 보도록 하자.

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 member = new Member();
        member.setUsername("hello world");
        member.setTeam(team);
        em.persist(member);

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

        Member m = em.find(Member.class, member.getId());
        System.out.println("team = " + m.getTeam().getClass());

        tx.commit();
    } catch(Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        em.close();
    }

    emf.close();
}

팀, 멤버를 저장한 후에 find 메서드로 멤버 객체를 받아왔다.

그리고 멤버 객체의 team 객체를 가져와서 해당 객체의 클래스를 살펴봤더니 프록시이다.

즉, member 에 대한 select 쿼리가 수행되고 team 에 대한 select 쿼리는 수행되지 않았다는 것을

예측할 수 있다.

 

그렇다면 위에서 봤던 프록시의 성질을 이용해서 team 의 name 을 가져오는 메서드를 호출하게

된다면 teamProxy 가 영속성 컨텍스트에게 초기화 요청을 하게 되므로 실제 team 에 대한 객체가

생성되게 되는데 이 시점에 select 쿼리가 수행되게 된다.

위 과정에 대한 코드는 다음과 같다.

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 member = new Member();
        member.setUsername("hello world");
        member.setTeam(team);
        em.persist(member);

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

        Member m = em.find(Member.class, member.getId());
        System.out.println("team.name = " + m.getTeam().getName());

        tx.commit();
    } catch(Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        em.close();
    }

    emf.close();
}

 

팀 프록시 객체의 getName 을 호출하게 됨으로써 실제 팀 객체가 NULL 이기 때문에

select 쿼리가 호출되고 정상적으로 team 의 이름을 가져온 것을 확인할 수 있다.

 

 

 

위와 같은 구조로 Member 에 대한 find 메서드를 호출하면 Member 테이블을 select 하는 쿼리가

호출되면서 member1 객체가 생성되고, 애너테이션을 통해 LAZY 로 설정을 해줬기 때문에

member1.team 이 참조하는 객체는 프록시 team1 이 된다.

 

 

만약 Member 객체의 프록시 team 객체에서 name 을 가져오는 메서드를 호출하게 된다면,

그 시점에 프록시 team 객체는 실제 team 객체가 있는지 확인하고 없을 경우 영속성 컨텍스트에

초기화 요청을 보내게 됨으로써 실제 team 객체가 생성되게 된다.

즉, select 쿼리가 실제 team 객체가 필요한 시점에서 실행된다는 것이다.

 

 

※ 즉시 로딩 EAGER 를 사용해서 함께 조회

 

@Entity
public class Member extends BaseEntity {

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    
    ...
}

 

이번엔 fetch 속성을 FetchType.EAGER 로 변경하여 즉시 로딩을 적용하였다.

즉시 로딩으로 Member 와 Team 을 같이 가져올 수 있도록 join 이 적용된 것을 확인할 수 있다.

 

 

 

LAZY 와 다르게 한번에 team 까지 땡겨오게 되므로 select 쿼리는 한번에 두 개가 호출되게 된다.

그러나 JPA 구현체는 가능하면 조인을 사용해서 SQL 을 한번에 함께 조회한다.

 

 

※ 프록시와 즉시로딩 주의

 

가급적 지연 로딩만 사용한다. (특히 실무에서)

즉시 로딩을 적용하면 예상하지 못한 SQL 이 발생하게 된다.

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

@ManyToOne, @OneToOne 은 기본이 즉시 로딩이다. (LAZY 로 설정해야 함)

@OneToMany, @ManyToMany 는 기본이 지연 로딩이다.

 

 

※ 지연 로딩 활용

 

 

Member 와 Team 은 자주 함께 사용되는 경우 : 즉시 로딩 (EAGER)

Member 와 Order 는 가끔 사용되는 경우 : 지연 로딩 (LAZY)

Order 와 Product 는 자주 함께 사용되는 경우 : 즉시 로딩 (EAGER)

 

 

그러나 실무에서는 모든 연관관계에 지연 로딩을 사용하며 즉시 로딩을 사용하면 안된다.

JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해야 한다.

즉시 로딩은 상상하지 못한 쿼리가 나가는 경우가 존재한다.

 

 

영속성 전이 : CASCADE

 

 

특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 사용된다.

예를 들어 위와 같이 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장하고 싶다.

위와 같은 구조의 Parent, Child 클래스를 아래와 같이 생성하였다.

@Entity
public class Parent {

    @Id
    @GeneratedValue
    Long id;

    String name;

    @OneToMany(mappedBy = "parent")
    private List<Child> childList = new ArrayList<>();
 
 	...
}
    
@Entity
public class Child {

    @Id
    @GeneratedValue
    Long id;

    private String name;

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

 

그리고 Parent 클래스 객체 parent 는 Child 클래스 객체 child1, child2 를 삽입하여 저장하는 코드를 살펴보자.

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(child1);
        em.persist(child2);
        em.persist(parent);

        tx.commit();
    } catch(Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        em.close();
    }

    emf.close();
}

 

persist 를 총 3번 했으니 insert 쿼리가 세 번 나타나게 되었다.

그런데 child 도 따로 persist 하는 과정이 귀찮아서 이러한 과정을 없애고 싶다.

즉, parent 위주로 persist 를 할 때 child 는 알아서 parent 가 저장될 때 알아서 저장되도록 하고 싶은 것이다.

이러한 방법을 돕는게 CASCADE, 영속성 전이 이다.

 

아래와 같이 코드를 수정하면 된다.

@Entity
public class Parent {

    @OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
    private List<Child> childList = new ArrayList<>();
    
    ...
}

 

위와 같이 cascade 에 CascadeType.PERSIST 값을 주게 되면, em.persist(parent) 만으로도 알아서

child1, child2 가 저장되는 것을 볼 수 있다.

 

 

※ 영속성 전이 : CASCADE - 주의할 점

 

영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없다.

엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐이다.

 

 

※ CASCADE 의 종류

 

- ALL : 모두 적용

- PERSIST : 영속

- REMOVE : 삭제

 

 

※ 고아 객체

 

고아 객체 제거란 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 것을 의미한다.

" orphanRemoval = true " 를 설정함으로써 자동으로 삭제가 이뤄진다.

 

아래 코드를 통해 알아보도록 하자.

Parent 클래스에 대한 코드는 다음과 같다.

@Entity
public class Parent {

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Child> childList = new ArrayList<>();
    
    ...
}

 

그리고 parent 객체에 child 객체 2 개가 속해있다고 할 때, childList 의 인덱스 0을 제거하는 코드를 살펴보자.

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

 

parent 객체의 childList 에서 인덱스 0 에 속해있던 child 객체는 remove 를 하게 되면

orphanRemoval = true 로 인해서 고아 객체를 제거하게 되는데 위와 같이 반영된 것을 확인할 수 있다.

 

 

※ 고아 객체 - 주의

 

참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능이다.

참조하는 곳이 딱 하나일 때 사용해야 한다.

특정 엔티티가 개인 소유할 때만 사용해야 한다.

@OneToOne, @OneToMany 만 가능하다.

 

참고로 개념적으로 부모를 제거하면 자식은 고아가 된다.

따라서 고아 객체 제거 기능을 활성화 하게 될 경우, 부모를 제거할 때 자식도 함께 제거된다.

즉, CascadeType.REMOVE 처럼 동작된다.

실제로 부모를 제거하면 자식도 같이 제거되는지를 확인해보자.

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());
        em.remove(findParent);

        tx.commit();
    } catch(Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        em.close();
    }

    emf.close();
}

 

위와 같이 부모를 제거하면 자식들은 전부 고아 객체가 되므로 자동으로 제거된다.

 

 

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

 

CascadeType.ALL + orphanRemoval = true

스스로 생명주기를 관리하는 엔티티는 em.persist() 로 영속화, em.remove() 로 제거한다.

두 옵션을 모두 활성화하게 될 경우, 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있다.

도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용하다.

 

 

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

 

모든 연관관계를 지연 로딩으로 바꿔야 한다. (실무에서 특히)

@ManyToOne, @OneToOne 은 디폴트가 즉시 로딩이므로 지연 로딩으로 변경해야 한다.

Order -> Delivery, OrderItem 을 영속성 전이를 ALL 로 설정하자.

 

'JPA' 카테고리의 다른 글

객체지향 쿼리 언어1 - 기본 문법  (0) 2022.09.01
값 타입  (0) 2022.08.16
고급 매핑  (0) 2022.08.12
다양한 연관관계 매핑  (0) 2022.08.10
연관관계 매핑 기초  (0) 2022.08.10
Comments