
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의 사실을 인지하지 못했기 때문에 발생한 일이라고 생각된다.
그래도 문제 원인을 분석하고, 해결하는 과정에서 개발의 또 다른 즐거움을 얻을 수 있었다.