Spring/팀스파르타

41. 영속성 전이

열심히 해 2024. 12. 6. 14:55

 

음식 테이블과 고객 테이블이 N : 1 양방향 관계라 가정해보겠습니다.

 

@SpringBootTest
public class CascadeTest {

    @Autowired
    UserRepository userRepository;
    @Autowired
    FoodRepository foodRepository;

    @Test
    @DisplayName("Robbie 음식 주문")  // cascade = CascadeType.PERSIST 사용 X
    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() 메서드를 호출하면서 영속화해야합니다.
  • JPA에서는 이를 간편하게 처리할 수 있는 방법으로 영속성 전이(CASCADE)의 PERSIST 옵션을 제공합니다.
    • 영속성 전이: 영속 상태의 Entity에서 수행되는 작업들이 연관된 Entity까지 전파되는 상황을 뜻합니다.
    • 영속성 전이를 적용하여 해당 Entity를 저장할 때 연관된 Entity까지 자동으로 저장하기 위해서는, 자동으로 저장하려고 하는 연관된 Entity에 추가한 연관관계 애너테이션에 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);// 외래 키(연관 관계) 설정
    }
}

 

이후

 

@Test
@DisplayName("영속성 전이 저장")
void test2() {
    // 고객 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);
}

 

 

userRepository.save(user); 만 호출해도 고객과 음식에 관한 데이터가 전부 저장됩니다.

 

 

 

 

CASCADE : REMOVE

  • 이번에는 연관된 Entity를 손쉽게 삭제하는 방법에 대해서 학습하겠습니다.
  • Robbie가 주문 APP을 탈퇴하려고 합니다.
    • 주문한 음식 정보들을 모두 삭제하려고 하는데 어떻게 하면 될까요?

 

 

CascadeType.REMOVE 를 설정하지 않고 고객 및 음식 정보 삭제하기.

@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를 삭제합니다.

 

  • 고객 Entity의 @OneToMany 애너테이션에 연관된 음식 Entity도 자동으로 삭제될 수 있도록 REMOVE 옵션을 추가합니다.
  • cascade = {CascadeType.PERSIST, CascadeType.REMOVE} 이렇게 중복으로 옵션을 설정할 수도 있습니다.

 

@Test
@Transactional
@Rollback(value = false)
@DisplayName("영속성 전이 삭제")
void test4() {
    // 고객 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());
    }

    // Robbie 탈퇴
    userRepository.delete(user);
}

 

고객 Entity - Robbie 객체를 조회한 후 해당 객체를 delete 하자 자동으로 연관된 음식 데이터들이 삭제되었습니다.