[Spring] Cascade(영속성 전이), orphanRemoval

thingzoo·2023년 7월 4일
0

Spring

목록 보기
46/54
post-thumbnail

Cascade(영속성 전이)

👀 영속성 전이란?
영속 상태의 Entity에서 수행되는 작업들이 연관된 Entity까지 전파되는 상황

옵션정보
CascadeType.ALL모두 적용
CascadeType.PERSIST영속
CascadeType.MERGE병합
CascadeType.REMOVE삭제
CascadeType.REFRESHREFRESH
CascadeType.DETACHDETACH

영속성 상태 저장(CascadeType.PERSIST)

음식 테이블과 고객 테이블이 N : 1 양방향 관계라 가정후
고객 ‘Robbie’가 후라이드 치킨과 양념 치킨을 주문해보겠다.

@Test
@DisplayName("Robbie 음식 주문")
void test1() {
    // 고객 Robbie 가 후라이드 치킨과 양념 치킨을 주문합니다.
    User user = new User();
    user.setName("Robbie");

    // 후라이드 치킨 주문
    Food food = new Food();
    food.setName("후라이드 치킨");
    food.setPrice(15000);

    user.addFoodList(food);

    Food food2 = new Food();
    food2.setName("양념 치킨");
    food2.setPrice(20000);

    user.addFoodList(food2);

    userRepository.save(user);
    foodRepository.save(food);
    foodRepository.save(food2);
}
  • Robbie가 음식을 주문하기 위해서는 위 처럼 user, food, food2 모두 save() 메서드를 3번 호출하면서 영속화해야한다.
  • JPA에서는 이를 간편하게 처리할 수 있는 방법으로 영속성 전이(CASCADE)의 PERSIST 옵션을 제공한다!
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST) // 영속성 전이 설정
    private List<Food> foodList = new ArrayList<>();

		public void addFoodList(Food food) {
			  this.foodList.add(food);
			  food.setUser(this);// 외래 키(연관 관계) 설정
		}
}
  • cascade = CascadeType.PERSIST
    • 영속성 전이를 적용하여 해당 Entity를 저장할 때 연관된 Entity까지 자동으로 저장
    • 자동으로 저장하려는 연관된 Entity 에너테이션에 추가하면됨
  • 이제는 userRepository.save(user); 이 한줄 만으로도 food까지 자동으로 저장된다.

영속성 상태 삭제(CascadeType.REMOVE)

연관된 Entity를 손쉽게 삭제하는 방법도 있다.

Robbie가 주문 APP을 탈퇴하려고 한다.
이때 주문한 음식 정보까지 모두 삭제하려고 한다.

@Test
@Transactional
@Rollback(value = false)
@DisplayName("Robbie 탈퇴")
void test3() {
    // 고객 Robbie 를 조회합니다.
    User user = userRepository.findByName("Robbie");
    System.out.println("user.getName() = " + user.getName());

    // Robbie 가 주문한 음식 조회
    for (Food food : user.getFoodList()) {
        System.out.println("food.getName() = " + food.getName());
    }

    // 주문한 음식 데이터 삭제
    foodRepository.deleteAll(user.getFoodList());

    // Robbie 탈퇴
    userRepository.delete(user);
}
  • 주문한 음식 데이터를 삭제하기 위해서 지연 로딩된 음식 Entity들을 가져와 직접 삭제해준 후 Robbie 고객의 Entity를 삭제한다. 총 2번의 삭제가 필요하다.
  • 마찬가지로 JPA에서는 이를 간편하게 처리할 수 있는 방법으로 영속성 전이(CASCADE)의 REMOVE 옵션을 제공한다.
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE}) // 중괄호로 중복 옵션 설정 가능
    private List<Food> foodList = new ArrayList<>();

    public void addFoodList(Food food) {
        this.foodList.add(food);
        food.setUser(this);// 외래 키(연관 관계) 설정
    }
}
  • cascade = {CascadeType.PERSIST, CascadeType.REMOVE}
    • 중복으로 옵션 설정 가능
  • cascade = CascadeType.REMOVE
    • 영속성 전이를 적용하여 해당 Entity를 삭제할 때 연관된 Entity까지 자동으로 삭제
    • 자동으로 삭제하려는 연관된 Entity 에너테이션에 추가하면됨
  • 이제는 userRepository.delete(user); 이 한줄 만으로도 food까지 자동으로 삭제된다.

orphanRemoval

  • CASCADE의 REMOVE 옵션을 적용하면 해당 Entity 객체를 삭제 했을 때 연관된 Entity 객체들을 자동으로 삭제할 수 있다.
    • 하지만, REMOVE 옵션 같은 경우 연관된 Entity와 관계를 제거했다고 해서 자동으로 해당 Entity가 삭제 되지는 않는다.
@Test
@Transactional
@Rollback(value = false)
@DisplayName("연관관계 제거")
void test1() {
    // 고객 Robbie 를 조회합니다.
    User user = userRepository.findByName("Robbie");
    System.out.println("user.getName() = " + user.getName());

    // 연관된 음식 Entity 제거 : 후라이드 치킨
    Food chicken = null;
    for (Food food : user.getFoodList()) {
        if(food.getName().equals("후라이드 치킨")) {
            chicken = food;
        }
    }
    if(chicken != null) {
        user.getFoodList().remove(chicken);
    }

    // 연관관계 제거 확인
    for (Food food : user.getFoodList()) {
        System.out.println("food.getName() = " + food.getName());
    }
}
  • 후라이드 치킨 Entity 객체와 연관관계를 제거했지만 Delete SQL이 수행되지는 않는다. 따라서 DB에서 후라이드가 고아가 되어버린다!

  • JPA에서는 이를 간편하게 처리할 수 있는 방법으로 orphanRemoval 옵션을 제공한다.

@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST, orphanRemoval = true)
    private List<Food> foodList = new ArrayList<>();

    public void addFoodList(Food food) {
        this.foodList.add(food);
        food.setUser(this);// 외래 키(연관 관계) 설정
    }
}
  • orphanRemoval=true
    • 부모 Entity와 연관관계가 끊어진 자식 Entity(고아)를 자동으로 삭제
    • 추가로 REMOVE 옵션처럼 해당 Entity 객체를 삭제하면 연관된 Entity들이 자동으로 삭제된다.
  • Delete SQL이 수행되어 후라이드 치킨 데이터가 삭제된 것을 확인할 수 있다.

🚨 orphanRemoval이나 REMOVE 옵션을 사용할 때 삭제하려고 하는 연관된 Entity를 다른 곳에서 참조하고 있는지 아닌지를 꼭 확인해야한다.

  • A와 B에 참조되고 있던 C를 B를 삭제하면서 같이 삭제하게 되면 A는 참조하고 있던 C가 사라졌기 때문에 문제가 발생할 수 있다.
  • 따라서 orphanRemoval 같은 경우 @ManyToOne 같은 애너테이션에서는 사용할 수 없다.
    • ManyToOne이 설정된 Entity는 해당 Entity 객체를 참조하는 다른 Entity 객체들이 있을 수 있기 때문에 속성으로 orphanRemoval를 가지고 있지 않는다.
profile
공부한 내용은 바로바로 기록하자!

0개의 댓글