레넌의 개발 일기
영속성 전이 - CascadeType.PERSIST와 CascadeType.REMOVE 본문
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이 기능을 사용하면 된다.
CascadeType.PERSIST
아래와 같이 Article 엔티티와 Aritcle과 Tag간의 중간 테이블인 ArticleTag 엔티티가 있다.
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Embedded
private Title title;
@Embedded
private Content content;
@Enumerated(value = EnumType.STRING)
@Column(nullable = false)
private Category category;
@OnDelete(action = OnDeleteAction.CASCADE)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;
@Embedded
private Views views;
@Embedded
private ArticleTags articleTags;
@Column(nullable = false)
private boolean isAnonymous;
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
public void addTag(Tag tag) {
articleTags.add(this, tag);
}
}
@Entity
public class ArticleTag {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id")
private Article article;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tag_id")
private Tag tag;
}
@Embeddable
public class ArticleTags {
@OneToMany(mappedBy = "article")
private List<ArticleTag> value;
public void add(Article article, Tag tag) {
value.add(new ArticleTag(article, tag));
}
}
@Entity
public class Tag {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false, length = MAX_NAME_LENGTH)
private String name;
}
게시글에 대한 태그를 2개 저장한다고 하면 아래와 같이 코드를 작성할 수 있다.
@Test
void test() {
// 게시글 저장
Article article = articleRepository.save(new Article("제목", "content", Category.QUESTION, member, false));
// 첫번째 태그 저장
Tag spring = new Tag("SPRING");
tagRepository.save(spring);
articleTagRepository.save(new ArticleTag(article, spring));
article.addTag(spring);
// 두번째 태그 저장
Tag java = new Tag("JAVA");
tagRepository.save(java);
articleTagRepository.save(new ArticleTag(article, java));
article.addTag(java);
}
Article을 영속상태로 만들고, ArticleTag도 영속상태로 만들어야 한다. 하지만 이럴 때 영속성 전이를 사용하면 Article만 영속 상태로 만들면 연관된 ArticleTag까지 한번에 영속 상태로 만들 수 있다. cascade = CascadeType.PERSIST 옵션을 설정해주었다.
@Embeddable
public class ArticleTags {
@OneToMany(mappedBy = "article", cascade = CascadeType.PERSIST)
private List<ArticleTag> value;
}
@Test
void test() {
// 게시글 저장
Article article = articleRepository.save(new Article("제목", "content", Category.QUESTION, member, false));
// 첫번째 태그 저장
Tag spring = new Tag("SPRING");
tagRepository.save(spring);
article.addTag(spring);
// 두번째 태그 저장
Tag java = new Tag("JAVA");
tagRepository.save(java);
article.addTag(java);
testEntityManager.flush();
testEntityManager.clear();
}
Cascade 옵션을 적용하기 전 ArticleTagRepository를 통해 값을 저장했을 때와 Cascade를 적용한 후, 동일하게 쿼리가 나가는 것을 확인할 수 있다.
영속성 전이는 연관관계를 매핑하는 것과는 아무 관련이 없다. 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화하는 편리함을 제공한다.
CascadeType.REMOVE
저장한 Article과 ArticleTag 엔티티를 모두 제거하기 위해서는 아래와 같이 각각의 엔티티를 하나씩 제거해야한다.
@Test
void test() {
// 게시글 저장
Article article = articleRepository.save(new Article("제목", "content", Category.QUESTION, member, false));
// 첫번째 태그 저장
Tag spring = new Tag("SPRING");
tagRepository.save(spring);
article.addTag(spring);
// 두번째 태그 저장
Tag java = new Tag("JAVA");
tagRepository.save(java);
article.addTag(java);
// 영속성 컨텍스트 비움
testEntityManager.flush();
testEntityManager.clear();
Article foundArticle = articleRepository.findById(article.getId()).get();
ArticleTag firstArticleTag = articleTagRepository.findById(1L).get();
ArticleTag secondArticleTag = articleTagRepository.findById(2L).get();
articleRepository.delete(foundArticle);
articleTagRepository.delete(firstArticleTag);
articleTagRepository.delete(secondArticleTag);
// 영속성 컨텍스트 비움
testEntityManager.flush();
testEntityManager.clear();
}
CascadeType.REMOVE로 옵션을 설정하고 아래 코드처럼 Article 엔티티만 삭제하면 연관된 ArticleTag 엔티티도 함께 삭제된다.
@Embeddable
public class ArticleTags {
@OneToMany(mappedBy = "article", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<ArticleTag> value;
}
@Test
void test() {
// 게시글 저장
Article article = articleRepository.save(new Article("제목", "content", Category.QUESTION, member, false));
// 첫번째 태그 저장
Tag spring = new Tag("SPRING");
tagRepository.save(spring);
article.addTag(spring);
// 두번째 태그 저장
Tag java = new Tag("JAVA");
tagRepository.save(java);
article.addTag(java);
// 영속성 컨텍스트 비움
testEntityManager.flush();
testEntityManager.clear();
Article foundArticle = articleRepository.findById(article.getId()).get();
articleRepository.delete(foundArticle);
// 영속성 컨텍스트 비움
testEntityManager.flush();
testEntityManager.clear();
}
Cascade 옵션을 적용하기 전 ArticleTagRepository를 통해 값을 제거했을 때와 Cascade를 적용한 후, 동일하게 쿼리가 나가는 것을 확인할 수 있다. 삭제 순서는 외래 키 제약조건을 고려해서 자식을 먼저 삭제하고 부모를 삭제한다.
CascadeType.REMOVE를 설정하지 않고 위의 코드를 실행하면 어떻게 될까?
Article 엔티티만 삭제된다. 하지만 데이터베이스의 Article 로우를 삭제하는 순간 자식 테이블에 걸려 있는 외래 키 제약조건으로 인해, 외래키 무결성 예외가 발생한다.
Cascade의 종류
public enum CascadeType {
/** Cascade all operations */
ALL,
/** Cascade persist operation */
PERSIST,
/** Cascade merge operation */
MERGE,
/** Cascade remove operation */
REMOVE,
/** Cascade refresh operation */
REFRESH,
/**
* Cascade detach operation
*
* @since 2.0
*
*/
DETACH
}
CascadeType 코드를 보면 다양한 옵션이 있는 것을 확인할 수 있다.
ALL | 모두 적용 |
PERSIST | 영속 |
MERGE | 병합 |
REMOVE | 삭제 |
REFRESH | REFRRESH |
DETACH | DETACH |
CascadeType.PERSIST, CascadeType.REMOVE는 플러시를 호출할 때 전이가 발생한다.
'Spring Data' 카테고리의 다른 글
@EnableJpaAuditing을 분리해야하는 이유 (1) | 2022.07.14 |
---|