[JPA] 영속성 전이와 고아객체
영속성 전이란, 부모가 자식의 영속성을 관리하는 기술이다.
영속성 전이는 주로 '합성관계'에서 사용되어야 한다. 합성관계는 부모가 자식을 제어하는 관계이다. 예를들어, MacBook은 일체형 노트북이다. MacBook노트북과 M1칩은 생명주기가 같다. 반면 조립형 컴퓨터는 다르다. 생산시기도 다른 부품들로 컴퓨터가 구성된다. 부모가 존재하기에 자식도 존재하는 관계가 합성관계이다. 이런 경우, 자식의 제어권은 부모에게 있기에 영속성도 부모의 제어권에 있도록 하는게 관리에 편하다.
MacBook 엔티티
@Entity
@Data
public class MacBook {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "CPU_ID")
private CPU cpu;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "RAM_ID")
private RAM ram;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "MAINBOARD_ID")
private MainBoard mainBoard;
public MacBook() { //생명주기를 같이함
this.cpu = new CPU();
this.ram = new RAM();
this.mainBoard = new MainBoard();
}
}
MacBook 엔티티 생성자를 보면 CPU, RAM, MainBoard는 MacBook 객체가 생성될 때 동시에 생성된다. 다시 말해서, MacBook과 생명주기를 같이한다. 그러므로 영속성도 같이해야 한다. 영속성 전이를 위해 연관관계 매핑에서 cascade 설정을 추가하였다. MackBook이 영속화 되면 자동으로 CPU,RAM,MainBoard도 영속화 된다.
public class Main {
public static void main(String[] args) {
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("h2");
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
try {
MacBook macBook = new MacBook();
macBook.setName("MINGU_MACBOOK");
entityManager.persist(macBook);
tx.commit();
}catch (Exception e) {
e.printStackTrace();
tx.rollback();
}finally {
entityManager.close();
}
}
}
MacBook 객체를 생성하고 MacBook만 영속화 한 뒤, 커밋을 해보았다.
그랬더니 영속화 하지 않았던, CPU,RAM,MainBoard도 동시에 INSERT 되어지는 것을 확인할 수 있다. 영속성전이로 이들도 모두 영속화 되어 관리되고 있던 것이다. H2콘솔을 확인하면 연관된 엔티티들이 모두 테이블이 생성되어 있다.
그럼 부모 엔티티를 영속성컨텍스트에서 제거해보자.
MacBook macBook = new MacBook();
macBook.setName("MINGU_MACBOOK");
entityManager.persist(macBook);
entityManager.flush();
entityManager.remove(macBook); // 영속성 컨텍스트에서 제거
MacBook 엔티티를 영속화하고 Flush하여 DB에 반영했다가 다시 remove()로 영속성컨텍스트에서 제거해보았다.
MacBook의 DELETE문이 실행되었을뿐 아니라, CPU, MainBoard,RAM 모두 DELETE문이 실행되었다.
이처럼 영속성 전이는 부모에 따라 자식도 영속화가 제어되는 것을 의미한다. 그러므로 연관관계가 합성관계인 객체사이에서 영속화 전이를 설정하는 것이 좋다. 일반적인 연관관계에는 위험할 수 있다. 일반적인 연관관계에서 영속화 전이를 설정하려면 자식 엔티티에 다른 객체가 의존해서는 안된다.
CascadeType에는 여러종류가 있다. 나는 ALL로 설정해서 모든 변경사항에 부모가 자식에게 전파하도록 설정했는데, 상황을 구체적으로 지정할 수 있다. CascadeType.PERSIST는 영속화만 전파한다. CascadeType.REMOVE는 영속화 제거만 전파한다. 이처럼 필요에 따라 적절한 전파종류를 선택하면 된다.
고아객체
영속성 전이는 부모가 영속화되면 자식도 영속화되고 부모가 영속화에서 제거되면 자식도 제거되었다. 그러나 부모는 영속상태로 있고 자식만 영속상태에서 제거해야하는 경우가 있다. 제거되어야 하는 자식객체를 고아객체라 부른다.
예를들어,
MacBook의 CPU가 고장나 새로운 CPU로 교체했다고 가정해보자. 고장난 CPU는 고아객체가 된다. 더이상 MacBook의 참조변수는 고장난 CPU를 가리키지 않는다. 그러므로 영속성컨텍스트에서도 제거되어야 한다. 영속성 전이 설정은 여기까지는 케어하지 못한다.
MacBook macBook = new MacBook();
macBook.setName("MINGU_MACBOOK");
entityManager.persist(macBook); // 영속화
entityManager.flush(); // DB에 저장
CPU cpu = new CPU(); // 새로운 CPU
cpu.setName("NEW CPU");
macBook.setCpu(cpu); // CPU 교체!
위 코드처럼 새로운 CPU를 생성하고 setter 메소드로 고장난 CPU를 교체를 해보았다. 그럼 어떻게 될까?
변경감지(DirtyChecking)로 UPDATE문이 실행되어 새로운 CPU로 변경되었지만 고장난 CPU는 DELETE 되지 않았다.
실제로 CPU 테이블에도 그대로 고장난 CPU레코드가 남아있다. 영속성 전이 설정만으로는 고아객체를 관리하지 못한다. 그러므로 고아객체를 제거하라는 설정이 필요하다.
@Entity
@Data
public class MacBook {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) // 고아객체 제거설정
@JoinColumn(name = "CPU_ID")
private CPU cpu;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "RAM_ID")
private RAM ram;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "MAINBOARD_ID")
private MainBoard mainBoard;
public MacBook() {
this.cpu = new CPU();
cpu.setName("OLD CPU");
this.ram = new RAM();
this.mainBoard = new MainBoard();
}
}
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 연관관계 매핑에 고아객체 제거설정을 true로 설정하였다 . 그럼 다시 프로그램을 실행해보자.
UPDATE문이 실행되어 새로운 CPU로 교체되고 DELETE문이 실행되어 고장난 CPU는 제거되었다.
CPU테이블에도 고장난 CPU 정보는 제거되었다.
이와같이, 전체-부분, 부모-자식 관계에서 생명주기를 같이하는 연관관계가 존재한다. 부모가 자식의 제어권을 가지고 있는 관계인 경우, 영속성 전이와 고아객체 관리를 통해 부모 하나만으로 다양한 자식의 영속성을 관리할 수 있다. 자식은 영속성 권한을 부모에게 맡기게 되므로 절대 다른 객체가 자식객체를 의존하는 경우가 있어서는 안 된다.
참고자료