👀 영속성 전이란?
영속 상태의 Entity에서 수행되는 작업들이 연관된 Entity까지 전파되는 상황
옵션 | 정보 |
---|---|
CascadeType.ALL | 모두 적용 |
CascadeType.PERSIST | 영속 |
CascadeType.MERGE | 병합 |
CascadeType.REMOVE | 삭제 |
CascadeType.REFRESH | REFRESH |
CascadeType.DETACH | DETACH |
음식 테이블과 고객 테이블이 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);
}
@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
userRepository.save(user);
이 한줄 만으로도 food까지 자동으로 저장된다.연관된 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번의 삭제가 필요하다.@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
userRepository.delete(user);
이 한줄 만으로도 food까지 자동으로 삭제된다.@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
🚨 orphanRemoval이나 REMOVE 옵션을 사용할 때 삭제하려고 하는 연관된 Entity를 다른 곳에서 참조하고 있는지 아닌지를 꼭 확인해야한다.
- A와 B에 참조되고 있던 C를 B를 삭제하면서 같이 삭제하게 되면 A는 참조하고 있던 C가 사라졌기 때문에 문제가 발생할 수 있다.
- 따라서 orphanRemoval 같은 경우 @ManyToOne 같은 애너테이션에서는 사용할 수 없다.
- ManyToOne이 설정된 Entity는 해당 Entity 객체를 참조하는 다른 Entity 객체들이 있을 수 있기 때문에 속성으로 orphanRemoval를 가지고 있지 않는다.