Error
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["FK7FUJUGL45E8HKHKIO47O2IW29: PUBLIC.ARTICLE FOREIGN KEY(ARTICLE_FILE_ID) REFERENCES PUBLIC.ARTICLE_FILE(ID) (CAST(1 AS BIGINT))"; SQL statement:
delete from article_file where id=? [23503-214]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
간단하게 표현하면 엔티티 간의 연관관계가 위와 같이 가지고 있는 상황이다. 그리고 ArticleFile
을 삭제하려고 할 때 발생하는 에러이다.
코드
불필요한 코드는 생략하여 보기 편하게 필요한 부분만 작성했다.
1. Article
@Table(name = "article")
@Entity
public class Article extends AuditingFields {
... (생략)
@OneToOne(fetch = LAZY)
@JoinColumn(name = "article_file_id")
private ArticleFile articleFile;
... (생략)
}
2. ArticleFile
@Table(name = "article_file")
@Entity
public class ArticleFile extends AuditingFields {
... (생략)
@OneToOne(mappedBy = "articleFile")
private Article article;
@OneToOne(fetch = LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "dimension_option_id")
private DimensionOption dimensionOption;
... (생략)
}
3. DimensionOption
@Table(name = "dimension_option")
@Entity
public class DimensionOption extends AuditingFields {
... (생략)
@ToString.Exclude
@OneToMany(mappedBy = "dimensionOption", fetch = LAZY, cascade = CascadeType.ALL)
private final Set<Dimension> dimensions = new LinkedHashSet<>();
@ToString.Exclude
@OneToOne(mappedBy = "dimensionOption")
private ArticleFile articleFile;
... (생략)
}
4. Dimension
@Table(name = "dimension")
@Entity
public class Dimension extends AuditingFields {
... (생략)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dimensionOptionId")
private DimensionOption dimensionOption;
... (생략)
}
문제
ArticleFile
이 Article
과 연결되어있는 상황에서 ArticleFile
을 삭제하게되면서 발생하는 문제이다. 정확히 말하면 참조 무결성을 해치면서 발생하는 문제이다. Article
은 ArticleFile
의 Foreign Key를 가지고 있는데 ArticleFile
을 삭제해버리면 존재하지 않는 엔티티의 ID를 참조하게 되는 것이다.
해결 방법
테스트 코드로 작성하여 설명한다.
1. 부모와의 관계를 끊고 삭제
@Test
void deleteArticleFile() {
Long articleFileId = 1L;
long previousFileCount = articleFileRepository.count();
//부모와의 관계를 끊는다
ArticleFile articleFile = articleFileRepository.getReferenceById(articleFileId);
Article article = articleRepository.getReferenceById(articleFile.getArticle().getId());
article.setArticleFile(null);
//삭제
articleFileRepository.deleteById(articleFileId);
assertThat(articleFileRepository.count()).isEqualTo(previousFileCount - 1);
}
NOTE
Article에서 CASCADE를 적용하더라도 위처럼 부모와의 관계를 끊고 삭제한다면 자식 엔티티를 삭제할 수 있다.
2. orphanRemoval 사용
orphanRemoval는 부모와의 관계가 끊어진 자식 엔티티들을 알아서 삭제해주는 기능이다. 따라서 따로 삭제를 수행하지 않아도 부모와의 관계만 끊어주면 된다.
@Table(name = "article")
@Entity
public class Article extends AuditingFields {
... (생략)
@OneToOne(fetch = LAZY, orphanRemoval = true) //<
@JoinColumn(name = "article_file_id")
private ArticleFile articleFile;
... (생략)
}
@Test
void deleteArticleFile() {
Long articleFileId = 1L;
long previousFileCount = articleFileRepository.count();
//부모와의 관계를 끊는다
ArticleFile articleFile = articleFileRepository.getReferenceById(articleFileId);
Article article = articleRepository.getReferenceById(articleFile.getArticle().getId());
article.setArticleFile(null);
assertThat(articleFileRepository.count()).isEqualTo(previousFileCount - 1);
}