Cascade
📌 영속성 전이(Cascade)란 JPA에서 특정 엔티티를 저장, 삭제 등의 작업을 할 때 연관된 엔티티에도 동일한 작업을 자동으로 적용하도록 설정하는 기능이다.
- 영속성 전이는 지연 로딩, 즉시 로딩과는 아무 관련이 없다.
코드 예시
- 1:N, N:1 양방향 연관관계
@Entity
@Table(name = "category")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "category")
private List<Product> productList = new ArrayList<>();
public Category() {
}
public Category(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void addProduct(Product product) {
productList.add(product);
product.setCategory(this);
}
}
@Entity
@Table(name = "product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "category_id")
private Category category;
public Product() {
}
public Product(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setCategory(Category category) {
this.category = category;
}
}
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("entity");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
try {
Category category = new Category("food");
em.persist(category);
Product product1 = new Product("pizza");
Product product2 = new Product("kimchi");
category.addProduct(product1);
category.addProduct(product2);
em.persist(product1);
em.persist(product2);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
실행결과
- 총 세번의 INSERT SQL이 실행된다.
- cascade 속성 적용
- CascadeType.ALL
@Entity
@Table(name = "category")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
private List<Product> productList = new ArrayList<>();
public Category() {
}
public Category(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void addProduct(Product product) {
productList.add(product);
product.setCategory(this);
}
}
public class CascadeMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("entity");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
try {
Category category = new Category("food");
Product product1 = new Product("pizza");
Product product2 = new Product("kimchi");
category.addProduct(product1);
category.addProduct(product2);
em.persist(category);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
실행결과
em.persist(category) 만 해도 세번의 INSER SQL이 실행된다.
사용 방법과 주의점
📌 영속성 전이는 연관관계 매핑과는 아무 관련이 없다.
- 영속성 전이(Cascade)
- 단순히 Entity를 저장, 삭제할 때 연관된 Entity에도 동일한 작업을 적용한다.
- 속성 종류
- ALL : 모두 적용
- PERSIST : 영속
- REMOVE : 삭제
MERGEREFRESHDETACH
- 사용 방법
- 단일 Entity에 완전히 종속적인 경우 생명주기가 같다면 사용한다.
- 블로그 글의 댓글처럼 항상 글을 통해서만 관리하는 경우
- 상품과 상품 이미지처럼 특정 상품에 종속되어 관리되는 경우
- 작가와 책
- 책은 특정 작가와 연관되지만 작가가 활동하지 않아도 책은 보존된다.
- 이런 경우는 사용하지 않는다.
- 단일 Entity에 완전히 종속적인 경우 생명주기가 같다면 사용한다.
고아 객체
📌 JPA에서 부모 엔티티와의 연관관계가 끊어진 자식 엔티티를 말한다.
- 고아 객체 삭제
- 부모 엔티티와 연관관계가 끊어진 자식 Entity를 자동으로 삭제한다.
- orphanRemoval = true 사용
- 기본 값 : false
@Entity
@Table(name = "category")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Product> productList = new ArrayList<>();
public Category() {
}
public Category(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public List<Product> getProductList() {
return productList;
}
public void addProduct(Product product) {
productList.add(product);
product.setCategory(this);
}
}
public class OrphanRemovalMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("entity");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
try {
Category category = new Category("food");
Product product1 = new Product("pizza");
Product product2 = new Product("kimchi");
category.addProduct(product1);
category.addProduct(product2);
em.persist(category);
em.flush();
em.clear();
Category findCategory = em.find(Category.class, category.getId());
findCategory.getProductList().remove(0);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
- 실행결과
- Collection에서 제거된 객체는 삭제된다.
- 주의점
- 참조하는 곳이 하나인 경우에만 사용한다.
- 단일 Entity에 완전히 종속적인 경우 생명주기가 같다면 사용한다.
- @OneToOne, @OneToMany만 사용이 가능하다.
- 부모 Entity를 제거하면 자식 Entity는 고아 객체가 된다.
- CascadeType.REMOVE와 비슷하게 동작한다.
- 참조하는 곳이 하나인 경우에만 사용한다.
CascadeType.ALL 제거
@Entity
@Table(name = "category")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "category", orphanRemoval = true)
private List<Product> productList = new ArrayList<>();
public Category() {
}
public Category(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public List<Product> getProductList() {
return productList;
}
public void addProduct(Product product) {
productList.add(product);
product.setCategory(this);
}
}
public class OrphanRemovalMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("entity");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
try {
Category category = new Category("food");
Product product1 = new Product("pizza");
Product product2 = new Product("kimchi");
category.addProduct(product1);
category.addProduct(product2);
em.persist(category);
// cascade 제거
em.persist(product1);
em.persist(product2);
em.flush();
em.clear();
// cascade 제거
Category findCategory = em.find(Category.class, category.getId());
em.remove(findCategory);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
실행결과
- Collection이 제거되었기 때문에 모두 삭제한다.