자바 ORM 표준 JPA 프로그래밍 - 기본편 수업을 듣고 정리한 내용입니다.
- 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이(
transitive persistence
) 기능을 사용하면 된다.JPA
는CASCADE
옵션으로 영속성 전이를 제공한다.- 영속성 전이를 사용하면 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장할 수 있다.
✔️ 부모가 여러 자식 엔티티를 가지고 있을 때
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);
}
그런데 이때, 자식들을 영속성 컨텍스트에 삽입하지 않을 시,
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
Parent
테이블만 생성된다. 이때, 영속성 전이를 사용해야 한다.
CASCADE
옵션 : 영속성 전이를 활성화
@Entity
public class Parent {
...
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> children = new ArrayList<Child>();
...
}
cascade = CascadeType.PERSIST
옵션을 설정했다.{
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);
}
CasecadeType.PERSIST
로 설정한 자식 엔티티까지 함께 영속화해서 저장한다.정상적으로 2건이 입력된다.
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);
public enum CascadeType {
ALL, // 모두 적용 : LIVE 사이클 모두 맞출 때
PERSIST, // 영속 : 부모 엔티티를 저장할 때 연관된 자식 엔티티도 같이 저장할 때 사용한다.
MERGE, // 병합
REMOVE, // 삭제
REFRESH, // REFRESH
DETACH // DETACH
}
여러 속성을 같이 사용 가능하다.
cascade = {CascadeType.PERSIST, CascadeType.REMOVE}
💡 참고
CascadeType.PERSIST
,CascadeType.REMOVE
는em.persist()
,em.remove()
를 실행할 때 바로 전이가 발생하지 않고 플러시를 호출할 때 전이가 발생한다.
- CASCADE는 Parent, Child 라이프사이클이 거의 유사할 때 사용
- CASCADE는 단일 소유자(Child를 소유하는 곳이 하나일 때, Child가 Parent 이외 Member 와 같은 다른 클래스에서 소유하지 않을 때)
고아 객체(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); // 자식 엔티티를 컬렉션에서 제거
orphanRemoval = true
옵션으로 인해 컬렉션에서 엔티티를 제거하면 데이터베이스의 데이터도 삭제된다. DELETE SQL
이 실행된다.
parent1.getChildren().clear();
📌 정리
고아 객체 제거 : 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
- 참조하는 곳이 하나일 때만 사용
- 특정 엔티티가 개인 소유하는 엔티티에만 이 기능을 적용해야 한다. (삭제한 엔티티를 다른 곳에서도 참조하면 문제가 발생)
- 이런 이유로
orphanRemoval
은@OneToOne
,@OneToMany
에만 사용할 수 있다!
💡 참고
- 부모를 제거하면 자식은 고아가 된다
- 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거하면 자식도 같이 제거된다. 이것은
CascadeType.REMOVE
처럼 동작한다.
✔️ 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 구현체들은 객체 그래프를 마음껏 탐색할 수 있도록 지원하는데 이때 프록시 기술을 사용한다.
- 객체를 조회할 때 연관된 객체를 즉시 로딩하는 방법을 즉시 로딩이라 하고, 연관된 객체를 지연해서 로딩하는 방법을 지연 로딩이라 한다.
- 객체를 저장하거나 삭제할 때 연관된 객체도 함께 저장하거나 삭제할 수 있는데 이것을 영속성 전이라 한다.
- 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하려면 고아 객체 제거 기능을 사용하면 된다.
✔️ 글로벌 페치 전략 설정
- 글로벌 페치 전략을 즉시 로딩으로 설정하면 사용하지 않는 엔티티도 함께 조회되므로 모두 지연 로딩으로 설정
@OneToOne
,@ManyToOne
의fetch
속성을 지연 로딩으로 수정
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);
}
...
}
참고