영속성 전이란 : CASCADE
- 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들어 주는 것을 의미합니다.
위의 예시로 Parent와 Child는 1:N 관계로 매핑되어 있습니다.
Child 엔티티는 한 Parent와 연결되어 있습니다.
이때 Parent 엔티티를 저장할 때 연관된 자식 엔티티도 함께 저장하는 것을 영속성 전이라 합니다.
영속성 전이 : CASCADE 주의
- 영속성 전이는 연관관계를 매핑하는 것과 관련이 없습니다.
- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리한 기능만 제공하는 것입니다.
CASCADE 종류
- ALL : 모두 적용
- PERSIST : 영속
- REMOVE : 삭제
- MERGE : 병합
- REFRESH : 리프레쉬
- DETACH : DETACH
모든 관계에 CASCADE.ALL을 적용하는 것이 좋은가?
No, 모든 관계에 CASCADE.ALL을 적용하게 되면 부모 엔티티를 삭제할 경우 자식의 데이터까지 삭제되게 됩니다.
그래서 권장하는 CASCADE 범위는, 완전히 개인 소유하는 엔티티일 경우에 적용하는 것이 좋습니다.
예를 들어, 게시판과 첨부파일인 경우 첨부파일은 게시판과 N:1 관계이고 게시판 엔티티만 참조하므로 개인 소유입니다.
또 다른 예시, Order -> OrderItem 또한 OrderItem이 Order만 참조하고 있으므로 하나의 Order를 삭제할 때 연관된 OrderItem도 같이 삭제합니다.
CASCADE 사용하기 애매한 경우
Order와 Delivery 인 경우 1:1로 연결되어 있으며, 두 엔티티 모두 다른 엔티티와 관계가 있으므로 함부로 동시에 삭제되어서는 안 됩니다. (회원, 상품 등등이 있습니다.)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<MemberAgree> memberAgreeList = new ArrayList<>();
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<MemberPrefer> memberPreferList = new ArrayList<>();
트러블 슈팅 - cascade.all 삭제가 안 돼요.
CASCADE.ALL로 연결된 자식 엔티티를 삭제가 안 되는 경우
이유 : 자식 엔티티를 삭제해도, 부모 -> 자식 연관관계가 CASCADE로 남아있기 때문에 삭제가 안됩니다.
em.remove()와 부모 -> 자식의 CASCADE 연관관계가 서로 충돌하게 됩니다.
하나는 지우려 하고, 하나는 CASCADE 관계로 연관관계에 객체가 남아있으니 저장하려고 합니다.
해결방법
부모 -> 자식의 연관관계를 끊어준 후 삭제하면 됩니다.
@DataJpaTest
class CascadeTestApplicationTests {
@Autowired
EntityManager em;
@Test
void cascade() {
//given
Post post = new Post(); // Post 객체 생성
Upload upload = new Upload(); // Upload 객체 생성
post.addUpload(upload);
em.persist(post); // Cascade로 동시 영속화 됨
em.flush(); // commit
em.clear(); // 영속성 컨텍스트 초기화
//when
Post findPost = em.find(Post.class, post.getId()); // post 엔티티 조회
Upload findUpload = findPost.getUploadList().get(0); // 연관된 UploadList 조회
// cascade 때문에 객체 연관관계 남아있으면 삭제 안됨,
// 객체 연관관계가 남아있으면,
// cascade 때문에 post -> upload 관계가 남아있다고 생각해서 삭제가 안됨
findPost.getUploadList().remove(findUpload);
// em.remove을 생략하려면 post -> upload 관계에 orphanRemoval=true 추가 필요
em.remove(findUpload);
em.flush();
em.clear();
//then: upload는 제거되어야 한다.
Upload removedUpload = em.find(Upload.class, upload.getId());
Assertions.assertThat(removedUpload).isNull();
}
}
https://www.inflearn.com/questions/56718/cascade-cascadetype-all%EC%A7%88%EB%AC%B8
위 사이트를 참고하여 정리하였습니다.
연관관계 편의 메서드란
양방향 연관관계를 맺을 때, 양쪽 모두 관계를 맺어 주어야 합니다.
사실 외래키를 관리하는 연관관계의 주인 (N 측) 쪽에만 관계를 맺어준다면 정상적으로 양쪽 모두 조회가 가능은 합니다.
Team team1 = new Team("Team1", "111");
em.persist(team1);
Member member1 = new Member("Member1","aaa");
member1.setTeam(team1); // 연관관계 설정 member1 -> team1
하지만 JPA는 ORM 방식이므로, 객체까지 고려하여 양쪽의 관계를 다 맺어주어야 좋습니다.
Team team1 = new Team("Team1", "111");
em.persist(team1);
Member member1 = new Member("Member1","aaa");
member1.setTeam(team1); // 연관관계 설정 member1 -> team1
team1.getMembers().add(member1) // 연관관계 설정 tema1 -> member1
즉, 객체의 양방향 연관관계는 양쪽 엔티티 모두 관계를 맺어주어야 순수한 객체 상태에서도 정상적으로 동작합니다.
그래서 연관관계 편의 메서드를 하나의 엔티티에 코드로 작성하여 안전하게 사용합니다.
// Member Entity에 작성 - 주로 N측, 연관관계 주인에 작성
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
연속적으로 setTeam을 호출한 경우,
team1에서 멤버를 조회하면 여전히 member1이 조회되는 문제가 있습니다.
=> team2로 변경할 때 team1과의 관계를 제거하지 않아 생겨 문제가 발생합니다.
member1.setTeam(team1);
member1.setTeam(team2);
해결방법
public void setTeam(Team team) {
if (this.team != null) { // 기존에 이미 팀이 존재한다면
this.team.getMembers().remove(this); // 관계를 끊는다.
}
this.team = team;
team.getMembers().add(this);
}
'Spring Framework > JPA' 카테고리의 다른 글
[JPA] Spring 엔티티 컬럼 기본값 설정하기 - @DynamicInsert, @ColumnDefault, @Builder.Default (0) | 2024.08.11 |
---|---|
[JPA] JPA 상속 관계 매핑과 @MappedSuperClass 사용하기 - 조인전략, 싱글 테이블, @CreatedDate, @LastModifiedDate (1) | 2023.11.28 |
[JPA] JPA 페치조인과 DISTINCT - 컬렉션 페치 조인 데이터 중복 문제 해결 (0) | 2023.11.17 |
[JPA] 연관관계 매핑, 양방향, 연관관계 주인, mappedBy (0) | 2023.11.11 |
[JPA] Spring JPA 프록시 객체와 지연로딩이란 무엇인가, 사용하는 이유 (1) | 2023.10.09 |