Content
는 @ManyToOne
연관관계를 사용하여, Card
와 N:1 연관관계를 갖는다.@ManyToOne
에는 어떠한 옵션도 들어가 있지 않다. 따라서 default fetch 옵션인 FetchType.EAGER
상태이다.Card
조회 테스트에서는 해당 카드의 아이디를 가진 Content
들이 List 형태로 함께 조회된다.Card
단 건 조회Card
전체 조회init()
메서드를 @BeforeEach
를 통해 실행한다.init()
메서드의 코드는 다음과 같다.@BeforeEach
void init() {
contentRepository.deleteAll();
createNotesAndCardsAndContents(NUMBER_OF_CARD); // test에 필요한 데이터 생성
}
CardFindTest
)에 존재하는 테스트를 수행할 시, 매 번 note table
과 card table
에 대한 truncate를 진행하고 있다.truncate.sql
은 다음과 같다.SET FOREIGN_KEY_CHECKS = 0; // 외래키 제약조건으로 인해 TRUNCATE가 제대로 되지 않을 수 있으므로 선언
TRUNCATE TABLE note;
TRUNCATE TABLE card;
SET FOREIGN_KEY_CHECKS = 1; // 외래키로 제약조건을 다시 원상태로 되돌림
content table
에 대한 truncate
는 진행되고 있지 않다는 점이다.org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find dev.whatevernote.be.service.domain.Card with id 1; nested exception is javax.persistence.EntityNotFoundException: Unable to find dev.whatevernote.be.service.domain.Card with id 1
truncate.sql
을 보면 알겠지만, note table
과 card table
에 대해서만 truncate를 진행하고 있다.Content
는 Card
의 id 값을 FK로 가지고 있다.deleteAll()
메서드를 수행해서 content table
을 비우려고 했다.deleteAll()
메서드는 cascade 관계(부모-자식 관계)를 파악하여 자식 객체들을 모두 조회 후에 제거하는 작업을 수행한다.deleteAll()
을 수행하였더니 다음과 같은 select 쿼리문을 날리는 것이 확인되었다.select card0_.id as id1_0_0_, card0_.created_at as created_2_0_0_, card0_.deleted as deleted3_0_0_, card0_.updated_at as updated_4_0_0_, card0_.note_id as note_id7_0_0_, card0_.card_order as card_ord5_0_0_, card0_.title as title6_0_0_, note1_.id as id1_2_1_, note1_.created_at as created_2_2_1_, note1_.deleted as deleted3_2_1_, note1_.updated_at as updated_4_2_1_, note1_.note_order as note_ord5_2_1_, note1_.title as title6_2_1_ from card card0_ left outer join note note1_ on card0_.note_id=note1_.id where card0_.id=? and ( card0_.deleted = 0)
deleteAll()
을 수행하면, 이미 card table
은 truncate가 되어 데이터가 존재하지 않는데, deleteAll()
은 casecade 관계를 파악할 때 부모 Entity 데이터가 존재하지 않아 Unable to find dev.whatevernote.be.service.domain.Card with id 1
와 같은 에러가 뜬 것이다.deleteAll()
메서드를 사용하지 않고, content table
에 대해서도 truncate를 해주는 것으로 문제를 해결하였다.Content
는 Card
를 @ManyToOne
어노테이션을 통해 연관관계를 유지한다.deleteAll()
메서드가 해당 도메인에 대한 cascade 관계를 파악하는데, 문제의 핵심은 cascade를 확인하는 과정에서 연관관계에 대한 fetch를 진행하게 되는데, @ManyToOne
, @OneToOne
의 경우 default strategy가 EAGER이다.Unable to find dev.whatevernote.be.service.domain.Card with id 1
가 떴던 것이다.LAZY
로 바꾸게 되면, 실제 Card
가 사용되기 전까지 조회를 진행하지 않는다. 따라서 Content
를 무사히 모두 삭제할 수 있게 된다.deleteAll()
메서드는 해당 도메인에 대한 cascade 관계 파악 후 테이블의 모든 데이터를 조회한다는 측면에서 성능상으로 불리하다는 단점이 있었다.오랜만에 팀 프로젝트를 건들면서, truncate의 사실을 인지하지 못했기 때문에 발생한 일이라고 생각된다.
그래도 문제 원인을 분석하고, 해결하는 과정에서 개발의 또 다른 즐거움을 얻을 수 있었다.