✅ 영속성 전이란?
JPA에서는 엔티티 객체가 영속성 상태에 있어야지 영속성 컨텍스트에서 관리가 가능하다. 영속성 전이란, 부모 엔티티의 영속성 상태가 자식 엔티티에게까지 전파되는 것을 뜻한다. 이로써 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장되거나, 삭제할 때 자식 엔티티도 함께 삭제할 수 있다.
🔻영속성 전이의 종류
- ALL: 모든 상태 변화(생성, 수정, 삭제)를 전파
- PERSIST: 새로운 엔티티를 생성할 때 전파
- MERGE: 엔티티를 병합(수정)할 때 전파
- REMOVE: 엔티티를 삭제할 때 전파
- REFRESH: 엔티티를 리프레시할 때 전파
- DETACH: 엔티티를 분리할 때 전파
🔹저장
- MemberCascade
@Entity public class MemberCascade { @Id @GeneratedValue @Column(name = "MEMBER_ID") private Long id; private String name; //@ManyToOne : default EAGER @ManyToOne @JoinColumn(name = "TEAM_ID") private TeamCascade teamCascade;
- TeamCascade : CascadeType.PERSIST
@Entity public class TeamCascade { @Id @GeneratedValue @Column(name = "TEAM_ID") private Long id; private String name; @OneToMany(mappedBy = "teamCascade", cascade = {CascadeType.PERSIST} ) private List<MemberCascade> memberCascades = new ArrayList<>();
- Main
package jpabook.model; import jpabook.model.entityCascade.MemberCascade; import jpabook.model.entityCascade.TeamCascade; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class Cascade { public static void main(String[] args) { //엔티티 매니저 팩토리 생성 EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook"); EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성 EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득 try { tx.begin(); //트랜잭션 시작 // saveWithCascade Long[] ids = saveWithCascade(em); tx.commit();//트랜잭션 커밋 em.clear(); } catch (Exception e) { e.printStackTrace(); tx.rollback(); //트랜잭션 롤백 } finally { em.close(); //엔티티 매니저 종료 } emf.close(); //엔티티 매니저 팩토리 종료 } public static Long[] saveWithCascade(EntityManager em) { System.out.println("========================== Save cascade ================================"); MemberCascade memberCascade1 = new MemberCascade("member1"); MemberCascade memberCascade2 = new MemberCascade("member2"); TeamCascade teamCascade1 = new TeamCascade("team1"); em.persist(teamCascade1); memberCascade1.setTeam(teamCascade1); memberCascade2.setTeam(teamCascade1); teamCascade1.getMembers().add(memberCascade1); teamCascade1.getMembers().add(memberCascade2); em.persist(teamCascade1); // 부모만 저장해도 자식까지 함께 저장 //== 다른 메소드에서 테스트를 연결해서 하기 위한 작업 ==// Long[] ids = new Long[5]; ids[0] = memberCascade1.getId(); ids[1] = memberCascade2.getId(); ids[2] = teamCascade1.getId(); return ids; } }
부모만 영속화하면 CascadeType.PERSIST로 설정한 자식 엔티티까지 함께 영속화해서 저장한다. 영속성 전이는 연관관계를 매핑하는 것과 관련이 없다. 단지 엔티티를 영속화할 때 연관된 엔티티까지 같이 영속화하는 편리함을 제공하는 것뿐이다.
🔹삭제
- TeamCascade : CascadeType.REMOVE
@Entity public class TeamCascade { @Id @GeneratedValue @Column(name = "TEAM_ID") private Long id; private String name; @OneToMany(mappedBy = "teamCascade", cascade = {CascadeType.PERSIST, CascadeType.REMOVE} ) private List<MemberCascade> memberCascades = new ArrayList<>();
- Main
package jpabook.model; import jpabook.model.entity.Team; import jpabook.model.entityCascade.MemberCascade; import jpabook.model.entityCascade.TeamCascade; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class Cascade { public static void main(String[] args) { //엔티티 매니저 팩토리 생성 EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook"); EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성 EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득 try { tx.begin(); //트랜잭션 시작 // saveWithCascade Long[] ids = saveWithCascade(em); Long member1Id = ids[0]; Long member2Id = ids[1]; Long team1Id = ids[2]; // removeParent removeParent(em, team1Id); tx.commit();//트랜잭션 커밋 em.clear(); } catch (Exception e) { e.printStackTrace(); tx.rollback(); //트랜잭션 롤백 } finally { em.close(); //엔티티 매니저 종료 } emf.close(); //엔티티 매니저 팩토리 종료 } public static Long[] saveWithCascade(EntityManager em) { System.out.println("========================== Save cascade ================================"); MemberCascade memberCascade1 = new MemberCascade("member1"); MemberCascade memberCascade2 = new MemberCascade("member2"); TeamCascade teamCascade1 = new TeamCascade("team1"); em.persist(teamCascade1); memberCascade1.setTeam(teamCascade1); memberCascade2.setTeam(teamCascade1); teamCascade1.getMembers().add(memberCascade1); teamCascade1.getMembers().add(memberCascade2); em.persist(teamCascade1); Long[] ids = new Long[5]; ids[0] = memberCascade1.getId(); ids[1] = memberCascade2.getId(); ids[2] = teamCascade1.getId(); return ids; } public static void removeParent(EntityManager em, Long team1Id) { TeamCascade team1 = em.find(TeamCascade.class, team1Id); em.remove(team1); } }
코드를 실행하면 부모는 물론 연관된 자식 테이블까지 삭제하며, DELETE SQL을 3번 실행한다. JPA는 외래 키 제약 조건을 고려해서 자식을 먼저 삭제하고 부모를 삭제한다.
🔹고아 객체
JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 고아 객체(ORPHAN) 제거라고 한다. 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제된다.
- TeamCascade
@Entity public class TeamCascade { @Id @GeneratedValue @Column(name = "TEAM_ID") private Long id; private String name; @OneToMany(mappedBy = "teamCascade", cascade = {CascadeType.PERSIST}, orphanRemoval = true // 고아 객체를 삭제 ) private List<MemberCascade> memberCascades = new ArrayList<>();
- Main
public class Cascade { public static void main(String[] args) { //엔티티 매니저 팩토리 생성 EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook"); EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성 EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득 try { tx.begin(); //트랜잭션 시작 // saveWithCascade Long[] ids = saveWithCascade(em); Long member1Id = ids[0]; Long member2Id = ids[1]; Long team1Id = ids[2]; tx.commit(); //트랜잭션 커밋 em.clear(); tx.begin(); //트랜잭션 시작 // removeParent //removeParent(em, team1Id); // removeOrphan removeOrphan(em, team1Id); tx.commit(); //트랜잭션 커밋 } catch (Exception e) { e.printStackTrace(); tx.rollback(); //트랜잭션 롤백 } finally { em.close(); //엔티티 매니저 종료 } emf.close(); //엔티티 매니저 팩토리 종료 } public static Long[] saveWithCascade(EntityManager em) { System.out.println("========================== Save cascade ================================"); MemberCascade memberCascade1 = new MemberCascade("member1"); MemberCascade memberCascade2 = new MemberCascade("member2"); TeamCascade teamCascade1 = new TeamCascade("team1"); em.persist(teamCascade1); memberCascade1.setTeam(teamCascade1); memberCascade2.setTeam(teamCascade1); teamCascade1.getMembers().add(memberCascade1); teamCascade1.getMembers().add(memberCascade2); em.persist(teamCascade1); // 부모만 저장해도 자식까지 함께 저장 //== 다른 메소드에서 테스트를 연결해서 하기 위한 작업 ==// Long[] ids = new Long[5]; ids[0] = memberCascade1.getId(); ids[1] = memberCascade2.getId(); ids[2] = teamCascade1.getId(); return ids; } public static void removeParent(EntityManager em, Long team1Id) { System.out.println("======================== Remove Parent ================================"); TeamCascade team1 = em.find(TeamCascade.class, team1Id); em.remove(team1); } public static void removeOrphan(EntityManager em, Long team1Id) { System.out.println("======================== Remove orphan ================================"); TeamCascade teamCascade = em.find(TeamCascade.class, team1Id); teamCascade.getMembers().clear(); // = CascadeType.REMOVE } }
참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하였다. ORPHAN이 적용되기 위해선 트랜잭션 안에서 동작해야 되서, stub 데이터를 저장 후 새로운 트랜잭션을 시작했다.
👉 위 기능은 참조하는 곳이 하나일 때만 사용해야 한다. 특정 엔티티가 개인 소유하는 엔티티에만 이 기능을 적용해야하며 다른 곳에서도 참조하는 엔티티에 사용할 경우 문제가 발생할 수 있다. 이런 이유로 orphanRemovel은 @OneToOne, @OneToMany에만 사용한다.
✅ 영속성 전이 + 고아 객체 : 생명주기
CascadeType.ALL + orphanRemoval = true
위 두 옵션을 모두 활성화하면 부모 엔티티를 통해서 자식 엔티티의 생명주기를 관리할 수 있다.
- TeamCascade : 연관관계 메소드 추가
public void addChild(MemberCascade memberCascade) { memberCascades.add(memberCascade); memberCascade.setTeam(this); }
- Main, mehtod 추가
public static void saveRemoveOrphan(EntityManager em, EntityTransaction tx) throws Exception { tx.begin(); //트랜잭션 시작 // saveWithCascade Long[] ids = saveWithCascade(em); Long member1Id = ids[0]; Long member2Id = ids[1]; Long team1Id = ids[2]; tx.commit(); //트랜잭션 커밋 em.clear(); tx.begin(); // removeParent removeParent(em, team1Id); // removeOrphan removeOrphan(em, team1Id); tx.commit(); } public static void allAndOrphan(EntityManager em, EntityTransaction tx) throws Exception { tx.begin(); //트랜잭션 시작 Long[] idsAll = allAndOrphanPersist(em); Long member1Id = idsAll[0]; Long member2Id = idsAll[1]; Long team1Id = idsAll[2]; tx.commit();//트랜잭션 커밋 em.clear(); tx.begin(); allAndOrphanRemove(em, member1Id, member2Id, team1Id); tx.commit(); }
- 전체 코드
package jpabook.model; import jpabook.model.entityCascade.MemberCascade; import jpabook.model.entityCascade.TeamCascade; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class Cascade { public static void main(String[] args) { //엔티티 매니저 팩토리 생성 EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook"); EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성 EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득 try { saveRemoveOrphan(em, tx); //allAndOrphan(em, tx); } catch (Exception e) { e.printStackTrace(); tx.rollback(); //트랜잭션 롤백 } finally { em.close(); //엔티티 매니저 종료 } emf.close(); //엔티티 매니저 팩토리 종료 } public static Long[] saveWithCascade(EntityManager em) { System.out.println("========================== Save cascade ================================"); MemberCascade memberCascade1 = new MemberCascade("member1"); MemberCascade memberCascade2 = new MemberCascade("member2"); TeamCascade teamCascade1 = new TeamCascade("team1"); em.persist(teamCascade1); memberCascade1.setTeam(teamCascade1); memberCascade2.setTeam(teamCascade1); teamCascade1.getMembers().add(memberCascade1); teamCascade1.getMembers().add(memberCascade2); em.persist(teamCascade1); // 부모만 저장해도 자식까지 함께 저장 //== 다른 메소드에서 테스트를 연결해서 하기 위한 작업 ==// Long[] ids = new Long[5]; ids[0] = memberCascade1.getId(); ids[1] = memberCascade2.getId(); ids[2] = teamCascade1.getId(); return ids; } public static void removeParent(EntityManager em, Long team1Id) { System.out.println("======================== Remove Parent ================================"); TeamCascade team1 = em.find(TeamCascade.class, team1Id); em.remove(team1); } public static void removeOrphan(EntityManager em, Long team1Id) { System.out.println("======================== Remove orphan ================================"); TeamCascade teamCascade = em.find(TeamCascade.class, team1Id); teamCascade.getMembers().clear(); // = CascadeType.REMOVE } public static Long[] allAndOrphanPersist(EntityManager em) { System.out.println("======================== All & Orphan Persist ========================="); MemberCascade memberCascade1 = new MemberCascade("member1"); MemberCascade memberCascade2 = new MemberCascade("member2"); TeamCascade teamCascade = new TeamCascade("team1"); teamCascade.addChild(memberCascade1); teamCascade.addChild(memberCascade2); em.persist(teamCascade); Long[] ids = new Long[5]; ids[0] = memberCascade1.getId(); ids[1] = memberCascade2.getId(); ids[2] = teamCascade.getId(); return ids; } public static void allAndOrphanRemove(EntityManager em, Long member1Id, Long member2Id, Long team1Id) { em.clear(); System.out.println("======================== All & Orphan Remove ========================="); MemberCascade memberCascade1 = em.find(MemberCascade.class, member1Id); MemberCascade memberCascade2 = em.find(MemberCascade.class, member2Id); TeamCascade teamCascade = em.find(TeamCascade.class, team1Id); teamCascade.getMembers().remove(memberCascade1); teamCascade.getMembers().remove(memberCascade2); } public static void saveRemoveOrphan(EntityManager em, EntityTransaction tx) throws Exception { tx.begin(); //트랜잭션 시작 // saveWithCascade Long[] ids = saveWithCascade(em); Long member1Id = ids[0]; Long member2Id = ids[1]; Long team1Id = ids[2]; tx.commit(); //트랜잭션 커밋 em.clear(); tx.begin(); // removeParent removeParent(em, team1Id); // removeOrphan removeOrphan(em, team1Id); tx.commit(); } public static void allAndOrphan(EntityManager em, EntityTransaction tx) throws Exception { tx.begin(); //트랜잭션 시작 Long[] idsAll = allAndOrphanPersist(em); Long member1Id = idsAll[0]; Long member2Id = idsAll[1]; Long team1Id = idsAll[2]; tx.commit();//트랜잭션 커밋 em.clear(); tx.begin(); allAndOrphanRemove(em, member1Id, member2Id, team1Id); tx.commit(); } }
Uploaded by N2T