Spring Data JPA로 설계에 대한 영속성 전이
@ManyToOne(cascade = CascadeType.ALL) // 여기
@JoinColumn(name = "channel_id", columnDefinition = "uuid")
private Channel channel;
Cascade(영속성 전이)
부모 엔티티의 영속 상태 변경이 자식 엔티티에 전이되도록 도와주는 기능
부모를 저장하거나 삭제하면 자식까지 함께 저장, 삭제되도록 함
영속성 전이 설정을 잘~ 해두면
1. 복잡한 도메인 구조에서 부모 - 자식의 관계가 명확할때, 부모에 변경사항이 있을 때 자식을 따로 건들 필요가 없다.
2. 트랜잭션 단위의 일관성을 유지해준다.
주의
- 잘못 사용하면 대량삭제, 엔티티 대량 추가로 이어질 수 있음
- 연관관계가 명확하지 않을경우, 데이터 손실로 이어질 수 있음
- 영속성 컨텍스트에 불필요하게 많은 엔티티를 올려 메모리를 낭비한다고 함..
Cascade의 종류
| PERSIST | 부모를 저장하면 자식도 자동으로 저장됨 |
| MERGE | 부모를 병합하면 자식도 자동으로 병합됨 |
| REMOVE | 부모를 삭제하면 자식도 자동으로 삭제됨 |
| REFRESH | 부모를 초기화하면 자식도 초기화됨 |
| DETACH | 부모를 detach? 하면 자식도 detach 된다고 한다.. |
가장 많이 사용하는건 PERSIST(저장), REMOVE(삭제) 라고 한다.
예제 및 주의사항
언제 써야하는가.. 사용해야할 때 : 부모 엔티티가 자식 엔티티의 생명주기를 완전히 관리할 때(user와 userstatus 같은 관계)
public class User {
//
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private UserStatus userStatus;
//
}
public class UserStatus {
//
@OneToOne(mappedBy = "userStatus")
@JoinColumn(name = "user_id", columnDefinition = "uuid")
private User user;
//
}
user와 userStatus는 1 대 1의 관계이다.
그래서 @OneToOne 애너테이션을 써주었고, mappedBy로 서로의 필드에 있는 객체의 이름을 적어주었다.
다만 UserStatus는 User에 온전히 종속되어야한다.
논리적으로 봤을 때 유저의 상태는 유저에 대한 정보에 해당한다. 이를 Entity형태로 따로 분류해둔거임
따라서 UserStatus가 삭제되어도 User의 데이터가 삭제될 일은 없어야한다.
유저의 상태가 삭제됐는데 유저 데이터가 날아가버리면 큰일이 난다!
그와 반대로 User가 삭제되면 해당 User에 대한 UserStatus는 의미없는 Entity가 되므로 삭제되어도 상관없다.
그래서 UserStatus쪽에는 cascade를 넣지 않았고
User쪽의 UserStatus 객체에는 cascade를 추가해줌으로 User객체가 삭제되었을 때 해당 User객체와 연관된 UserStatus도 함께 삭제되게끔 해주었다.
다만 CascadeType.ALL 이기 때문에 저장, 삭제, 병합, 초기화 등도 함께 이루어진다.
아무튼 서로의 엔티티, 특히 부모 엔티티가 자식 엔티티의 생명주기를 완전히 관리하는 상황일 때 cascade를 쓰면 매우 좋다.
그렇다면 언제 쓰지 말아야 하는가?
public class ReadStatus {
//
@ManyToOne(cascade = CascadeType.REMOVE)
@JoinColumn(name = "user_id", columnDefinition = "uuid")
private User user;
@ManyToOne(cascade = CascadeType.REMOVE)
@JoinColumn(name = "channel_id", columnDefinition = "uuid")
private Channel channel;
//
}
public class User {
}
public class Channel {
}
ReadStatus는 User와 Channel에 대해 다대일 단방향 맵핑이 되어있는 상황이다.
읽음상태? 읽기상태는 유저객체 하나와 채널객체 하나에 여러개의 ReadStatus가 들어갈 수 있음을 의미한다.
그리고 다대일 맵핑을 의미하는 @ManyToOne에 cascade = CascadeType.REMOVE 가 적용된 상태이다.
따라서 ReadStatus가 삭제되면 덩달아 user객체도 삭제되고 channel도 갑자기 폭발해버린다.
그러면 안되겠지?
그러니 저런 상황에서 cascade는 쓰면 안된다!
orphanRemoval(고아 객체 제거)
public class User {
//
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private UserStatus userStatus;
//
}
컬렉션에서 엔티티가 제거되면 자동으로 DB에서 삭제해주는 기능이다
부모가 자식을 더이상 참조하지 않는다면, DB에 남지 않도록 자동으로 삭제해줌
* 영속성 관계 안에서만 적용된다
* 엔티티의 상태가 명확하게 유지되어야한다
* remove와 동일하게 삭제된다
게시판 - 댓글의 관계, 주문 - 주문상세의 관계처럼
부모에 속한 자식이 독립된 객체가 되었을 때 존재 의미가 없을 경우 사용한다
* orphanRemoval은 JPA가 관리하는 컬렉션 안에서만 정상적으로 동작한다
// 관리하는 컬렉션에서 제거하기
user.getUserItems().remove(userStatus); // DB에서 자동적으로 삭제된다
쓰면 안되는 경우 : 자식 엔티티가 다른 부모와 연결되거나, 독립적으로 존재할 수 있는 경우에는 쓰면 안됨
공통점 : @OneToOne, @ManyToOne 애너테이션에 덧붙여 사용함
| Cascade | orphanRemoval | |
| 대상 | 연관관계가 명시된 엔티티 | 부모 컬렉션에서 제거되는 엔티티 |
| 동작 | 저장, 병합, 삭제, 초기화, detach 등 전이 | 컬렉션에서 제거하면 DB에서도 삭제됨 |
| 사용시기 | 관계의 생명주기가 명확할때 사용함 | 고아가 존재하면 안되는 도메인에 사용 |
잘못 사용하면 데이터 유실 위험, 대량 쿼리가 발생할 수 있다.
'Spring Boot' 카테고리의 다른 글
| Repository에서 @Query, QueryDSL 작성에 대해 (2) | 2025.08.02 |
|---|---|
| 즉시 로딩(EAGER), 지연 로딩(LAZY) (3) | 2025.07.26 |
| Spring Data JPA : Repository 작성 (0) | 2025.07.25 |
| Entity : 연관관계 맵핑 (3) | 2025.07.25 |
| Spring Data JPA : 구조 이해 (3) | 2025.07.23 |