프록시와 연관관계 관리 2

LeeKyoungChang·2022년 3월 11일
0
post-thumbnail

자바 ORM 표준 JPA 프로그래밍 - 기본편 수업을 듣고 정리한 내용입니다.

 

📚 1. 영속성 전이 : CASCADE

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이(transitive persistence) 기능을 사용하면 된다.
  • JPACASCADE 옵션으로 영속성 전이를 제공한다.
  • 영속성 전이를 사용하면 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장할 수 있다.

 

✔️ 부모가 여러 자식 엔티티를 가지고 있을 때

스크린샷 2022-03-11 오후 4 43 58

Parent

@Entity
public class Parent {

    @Id @GeneratedValue
    private Long id;
    
    @OneToMany(mappedBy = "parent")
    private List<Child> children = new ArrayList<Child>();
    ...
}
@Entity
public class Child {

    @Id @GeneratedValue
    private Long id;
    
    @ManyToOne
    private Parent parent;
    ...
}

ex) 부모 1명에 자식 2명을 저장

private static void saveNoCascade(EntityManager em) {

    // 부모 저장
    Parent parent = new Parent();
    em.persist(parent);
    
    // 1번 자식 저장
    Child child1 = new Child();
    child1.setParent(parent);          // 자식 -> 부모 연관관계 설정
    parent.getChildren().add(child1);  // 부모 -> 자식
    em.persist(child1);
    
    // 2번 자식 저장
    Child child2 = new Child();
    child2.setParent(parent);          // 자식 -> 부모 연관관계 설정
    parent.getChildren().add(child2);  // 부모 -> 자식
    em.persist(child2);
}
스크린샷 2022-03-28 오후 4 42 59
  • JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 한다.
  • 영속성 전이를 사용시, 부모만 영속 상태로 만들면 연관된 자식까지 한 번에 영속 상태로 만들 수 있다. (부모, 자식 엔티티 각각 영속 상태)

 

그런데 이때, 자식들을 영속성 컨텍스트에 삽입하지 않을 시,

            Child child1 = new Child();
            Child child2 = new Child();

            Parent parent = new Parent();
            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);
스크린샷 2022-03-28 오후 4 43 21

Parent 테이블만 생성된다. 이때, 영속성 전이를 사용해야 한다.

 

📖 A. 영속성 전이 : 저장

CASCADE 옵션 : 영속성 전이를 활성화

@Entity
public class Parent {
    ...
    @OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
    private List<Child> children = new ArrayList<Child>();
    ...
}
  • 부모를 영속화할 때 자식들도 함께 영속화하라고 cascade = CascadeType.PERSIST 옵션을 설정했다.
  • 이 옵션을 적용하면 아래 예제처럼 간편하게 부모와 자식 엔티티를 한 번에 영속화할 수 있다. (CASCADE 사용시 자식 테이블이 모두 생성된다.)
{
    
    Child child1 = new Child();
    Child child2 = new Child();
    
    Parent parent = new Parent();
    child1.setParent(parent);         // 연관관계 추가
    child2.setParent(parent);         // 연관관계 추가
    parent.getChildren().add(child1);
    parent.getChildren().add(child2);
    
    // 부모 저장, 연관된 자식들 저장
    em.persist(parent);
}
스크린샷 2022-03-11 오후 4 44 04
  • 부모만 영속화하면 CasecadeType.PERSIST로 설정한 자식 엔티티까지 함께 영속화해서 저장한다.

정상적으로 2건이 입력된다.

스크린샷 2022-03-11 오후 4 53 23
  • 영속성 전이는 연관관계를 매핑하는 것과는 아무 관련이 없다.
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공한다.
  • 위의 예제코드를 보면 양방향 연관관계를 추가한 다음 영속 상태로 만든 것을 확인할 수 있다.

 

📖 B. 영속성 전이 : 삭제

Parent findParent = em.find(Parent.class, 1L);
Child findChild1 = em.find(Child.class, 1L);
Child findChild2 = em.find(Child.class, 2L);

em.remove(findChild1);
em.remove(findChild2);
em.remove(fineParent);

 

✔️ 영속성 전이는 엔티티를 삭제할 때도 가능하다.
CascadeType.REMOVE로 설정하고 다음 코드처럼 부모 엔티티만 삭제하면 연관된 자식 엔티티도 함께 삭제된다.

Parent findParent = em.find(Parent.class, 1L);
em.remove(findParent);
  • 외래 키 제약조건을 고려해서 자식을 먼저 삭제하고 부모를 삭제한다.

 

📖 C. CASCADE의 종류

public enum CascadeType {
    ALL,       // 모두 적용 : LIVE 사이클 모두 맞출 때
    PERSIST,   // 영속 :  부모 엔티티를 저장할 때 연관된 자식 엔티티도 같이 저장할 때 사용한다.
    MERGE,     // 병합
    REMOVE,    // 삭제
    REFRESH,   // REFRESH
    DETACH     // DETACH
}

 

여러 속성을 같이 사용 가능하다.

cascade = {CascadeType.PERSIST, CascadeType.REMOVE}

 

💡 참고
CascadeType.PERSIST, CascadeType.REMOVEem.persist(), em.remove()를 실행할 때 바로 전이가 발생하지 않고 플러시를 호출할 때 전이가 발생한다.

  • CASCADE는 Parent, Child 라이프사이클이 거의 유사할 때 사용
  • CASCADE는 단일 소유자(Child를 소유하는 곳이 하나일 때, Child가 Parent 이외 Member 와 같은 다른 클래스에서 소유하지 않을 때)

 

📚 2. 고아 객체

고아 객체(ORPHAN) 제거 : JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공

고아 객체 기능을 사용하여 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제 예시

@Entity
public class Parent {

    @Id @GeneratedValue
    private Long id;
    
    @OneToMany(mappedBy = "parent",cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Child> childList = new ArrayList<>();
    ...
}
  • 고아 객체 제거 기능을 활성화하기 위해 컬렉션에 orphanRemoval = true를 설정
  • 컬렉션에서 제거한 엔티티는 자동으로 삭제된다.
            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());

            // child에서 0번째 인덱스 삭제
            findParent.getChildList().remove(0);   // 자식 엔티티를 컬렉션에서 제거
스크린샷 2022-03-28 오후 5 03 39
  • 컬렉션에서 첫 번째 자식을 제거했다. orphanRemoval = true 옵션으로 인해 컬렉션에서 엔티티를 제거하면 데이터베이스의 데이터도 삭제된다.
  • 고아 객체 제거 기능은 영속성 컨텍스트를 플러시할 때 적용되므로 플러시 시점에 DELETE SQL이 실행된다.

 

parent1.getChildren().clear();
  • 모든 자식 엔티티를 제거, 컬렉션을 비우기!

 

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

  • 참조하는 곳이 하나일 때만 사용
  • 특정 엔티티가 개인 소유하는 엔티티에만 이 기능을 적용해야 한다. (삭제한 엔티티를 다른 곳에서도 참조하면 문제가 발생)
    • 이런 이유로 orphanRemoval@OneToOne, @OneToMany에만 사용할 수 있다!

 

💡 참고

  • 부모를 제거하면 자식은 고아가 된다
  • 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거하면 자식도 같이 제거된다. 이것은 CascadeType.REMOVE처럼 동작한다.

 

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

✔️ CasecadeType.ALL + orphanRemoval = true를 동시에 사용

  • 일반적으로 엔티티는 EntityManager.persist()를 통해 영속화되고 EntityManager.remove()를 통해 제거된다.
  • 이것은 엔티티 스스로 생명주기를 관리한다는 뜻이다!
  • 그런데 두 옵션을 모두 활성화하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다.
  • 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용하다!

 

✔️ 자식을 저장하려면 부모에 등록만 하면 된다. (CASCADE)

Parent parent = em.find(Parent.class, parentId);
parent.addChild(child1);

 

✔️ 자식을 삭제하려면 부모에서 제거하면 된다(orphanRemoval)

Parent parent = em.find(Parent.class, parentId);
parent.getChildren().remove(removeObject);

 

📌 정리

  • JPA 구현체들은 객체 그래프를 마음껏 탐색할 수 있도록 지원하는데 이때 프록시 기술을 사용한다.
  • 객체를 조회할 때 연관된 객체를 즉시 로딩하는 방법을 즉시 로딩이라 하고, 연관된 객체를 지연해서 로딩하는 방법을 지연 로딩이라 한다.
  • 객체를 저장하거나 삭제할 때 연관된 객체도 함께 저장하거나 삭제할 수 있는데 이것을 영속성 전이라 한다.
  • 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하려면 고아 객체 제거 기능을 사용하면 된다.

 

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

✔️ 글로벌 페치 전략 설정

  • 글로벌 페치 전략을 즉시 로딩으로 설정하면 사용하지 않는 엔티티도 함께 조회되므로 모두 지연 로딩으로 설정
  • @OneToOne, @ManyToOnefetch 속성을 지연 로딩으로 수정

 

Order

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static javax.persistence.CascadeType.*;
import static javax.persistence.FetchType.*;

@Entity
@Table(name = "ORDERS")
public class Order extends BaseEntity{

    @Id
    @GeneratedValue
    @Column(name = "ORDER_ID")
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name="MEMBER_ID")
    private Member member;

    @OneToOne(fetch = LAZY, cascade = ALL)
    @JoinColumn(name = "DELIVERY_ID")
    private Delivery delivery;  // 베송정보
	
	...
}

 

OrderItem

import javax.persistence.*;

import static javax.persistence.FetchType.LAZY;

@Entity
@Table(name = "ORDER_ITEM")
public class OrderItem extends BaseEntity {

    @Id
    @GeneratedValue
    @Column(name = "ORDER_ITEM_ID")
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name="ORDER_ID")
    private Order order;

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

 

Category

import static javax.persistence.FetchType.*;

@Entity
public class Category extends BaseEntity{
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "PARENT_ID")
    private Category parent;
	...
}

 

Delivery

import javax.persistence.*;

import static javax.persistence.FetchType.*;

@Entity
public class Delivery extends BaseEntity {

    @Id @GeneratedValue
    private Long id;

    private String city;
    private String street;
    private String zipcode;
    private DeliveryStatus status;

    @OneToOne(mappedBy = "delivery", fetch = LAZY)
	...
}

 

✔️ 영속성 전이 설정

  • 엔티티를 영속 상태로 만들어서 데이터베이스에 저장할 때 연관된 엔티티도 모두 영속 상태여야 한다.
  • 연관된 엔티티 중에 영속 상태가 아닌 엔티티가 있으면 예외가 발생한다. (정확히는 플러시 시점에 오류가 발생한다)
  • 영속성 전이를 사용하면 연관된 엔티티를 편리하게 영속 상태로 만들 수 있다.
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static javax.persistence.FetchType.*;

@Entity
@Table(name = "ORDERS")
public class Order extends BaseEntity {

	...
    @OneToOne(cascade = CascadeType.ALL, fetch = LAZY) //**
    @JoinColumn(name = "DELIVERY_ID")
    private Delivery delivery;  //배송정보
    
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)    //**
    private List<OrderItem> orderItems = new ArrayList<OrderItem>();

    public void addOrderItem(OrderItem orderItem) {
        orderItems.add(orderItem);
        orderItem.setOrder(this);
    }

    public void setDelivery(Delivery delivery) {
        this.delivery = delivery;
        delivery.setOrder(this);
    }
    ...
}

 


참고

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글