[JPA] 영속성 전이 (CASCADE)와 고아 객체

19185 단어 JPAJPA

1. CASCADE

  • 특정 Entity를 영속 상태로 만들 때, 연관된 Entity들에 대해 영속성을 전파 시키는 옵션.
  • @OneToMany, @ManyToOne 어노테이션에서 사용이 가능하며 기본 설정으로 사용하지 않게 되어 있다.

예제

@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;
    
    @OneToMany(mappedBy = "person")
    private List<Address> addresses;
    
    public void addAddress(Address address){
    	addresses.add(address);
        addresses.setPerson(this);
    }
}

@Entity
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String street;
    private int houseNumber;
    private String city;
    private int zipCode;
    @ManyToOne(fetch = FetchType.LAZY)
    private Person person;
}
  • 위와 같은 엔티티들이 있다고 가정하자.

1. 기본적인 영속석 저장

Address address1 = new Address();
Address address2 = new Address();

Person person = new Person();

person.addAddress(address1);
person.addAddress(address2);

em.persist(person);
em.persist(address1);
em.persist(address2);
  • persist를 세 번 호출해야 정상적으로 동작함. 연관된 객체만큼 persist를 호출하므로 보기에도 비효율적인 코드다.

2. CASCADE를 이용한 영속성 저장

@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;
    
    @OneToMany(mappedBy = "person", cascade=CascadeType.ALL) 
    private List<Address> addresses;
    
    public void addAddress(Address address){
    	addresses.add(address);
        addresses.setPerson(this);
    }
}
  • 위와 같이 @OneToMany에서 cascade 속성을 정의해주자
Address address1 = new Address();
Address address2 = new Address();

Person person = new Person();

person.addAddress(address1);
person.addAddress(address2);

em.persist(person);
  • persist 메서드로 person을 저장하면 그와 연관된 엔티티 객체들도 같이 영속성 저장이 된다.

CASCADE의 타입

  • ALL: 모든 Cascade 타입 적용.
  • PERSIST: persist 메서드 호출 시 연관된 엔티티 저장
  • REMOVE: remove 메서드 호출 시 연관된 엔티티 제거
  • MERGE: merge 메서드 호출 시 연관된 엔티티들 병합
  • REFRESH: refresh 메서드 호출 시 인스턴스의 값을 다시 읽어 옴 (새로 고침)
  • DETACH: detach 메서드 호출 시 연관된 엔티티들까지 준영속 상태로 변환

영속성 전이를 사용할 수 있는 경우

  • 전이 대상이 한 군데에서만 사용되면 써도 좋으나, 하위 엔티티가 상위 엔티티에만 종속되지 않고 여러군데에서 사용되면 영속성 전이를 사용하지 않는게 좋다.
  • 요약하면
    • 서로의 라이프 사이클이 같을 때
    • 어떤 엔티티를 오직 자신만이 소유하고 있을 때, 즉 단일 소유자 관계인 경우
      • ex) 회원 엔티티와과 팀 엔티티가 연관되어있고, 회원 엔티티와 라커 엔티티가 연관되어 있다고 가정할 때, 팀을 삭제하면 회원 엔티티가 영속성 전이에 의해 지워진다. 라이프 사이클이 같지 않은 라커 엔티티는 회원 엔티티를 사용할 때 이슈가 발생하게 된다.

2. 고아 객체

  • 고아 객체는 부모 엔티티가 사라진 자식 엔티티를 말한다.

예제

1. 부모와 연관된 자식을 삭제했을 경우

public class Person {
	.
    .
    .
    @OneToMany(mappedBy = "person", cascade=CascadeType.ALL, orphanRemoval = true) 
	private List<Address> addresses;
    .
    .
}   
Address address1 = new Address();
Address address2 = new Address();

Person person = new Person();

person.addAddress(address1);
person.addAddress(address2);

em.flush();
em.clear();

Person person1 = em.find(Person.class, person.getId());
person.getAddresses().remove(0);
  • 두 개의 주소를 만들고 Person에 저장 후, Address Collection에 맨 처음 저장된 객체를 삭제했다. 기본적으로 Person이 주 관계이기 때문에 remove와 관련된 변화는 없어야한다.
  • 하지만 실제 remove 메서드가 실행 뒤 delete 쿼리가 DB에 날아가게 되고, DB에서도 하나의 row가 삭제된 것을 볼 수 있다.

2. 부모를 삭제했을 경우

Address address1 = new Address();
Address address2 = new Address();

Person person = new Person();

person.addAddress(address1);
person.addAddress(address2);

em.flush();
em.clear();

Person person1 = em.find(Person.class, person.getId());
em.remove(person1);
  • Person 객체와 연관된 모든 child들이 고아 객체가 되고 orphanRemoval이 true이므로 고아가 된 객체는 삭제가 된다.

영속성 전이 + 고아 객체 자동 삭제

  • 도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용하게 사용이 된다.
  • 부모 엔티티로 자식 엔티티의 생명 주기를 관리할 수 있다.
  • Cascade는 부모의 영속 상태가 변하면 자식도 같이 변한다. 즉 remove로 부모 객체 엔티티를 삭제하면 자식도 삭제되지만, 부모 엔티티 객체에서 자식 엔티티 객체를 삭제한다고 객체의 참조가 지워지는 것이지 자식 엔티티 객체가 지워지지 않는다.
  • 부모 객체에서 자식 객체를 지울 때는 orphanRemoval(고아객체 삭제)를 true로 설정하면 된다.

좋은 웹페이지 즐겨찾기