BE/Spring

[JPA] LazyLoading could not initialize proxy - no Session 문제 해결하기

E@st 2023. 10. 30. 02:35
// @Transaction 사용 X
@DisplayName("템플릿 수정 요청을 받아서 기존 템플릿을 업데이트한다.")
@Test
void updateTemplateTest() {
    var position = positionRepository.save(UserFixtures.createPosition());
    var user = UserFixtures.createUser(position);
    var savedUser = userRepository.save(user);

    var template = createTemplate(savedUser);
    var savedTemplate = templateRepository.save(template);

    var questions = List.of("변경된 질문1", "변경된 질문2", "변경된 질문3");
    var title = "변경된 제목";
    var request = new UpdateTemplateRequest(title, questions);
    templateService.updateTemplate(savedTemplate.getId(), request, AuthFixture.createAuthContext(savedUser.getId()));

    var findTemplate = templateRepository.findById(savedTemplate.getId()).get();

    Assertions.assertAll(
            () -> assertThat(findTemplate.getTitle()).isEqualTo(title),
            () -> assertThat(findTemplate.getContents()).isEqualTo(questions));
}

위에 테스트코드는 템플릿을 업데이트하는 로직의 테스트코드입니다. 위에 테스트에서 Template는 List<Question>을 OneToMany관계로 갖고있으며 fetchType을 Lazy로 사용하고 있습니다. 현재 팀 프로젝트에서는 @Transaction 어노테이션을 통한 롤백이 아닌 AfterEach를 통해 직접 데이터를 삭제해주고 있어서 findTemplate 에는 Question이 초기화되지 않은 상태이고 findTemplate.getContents()를 호출하면서 예외가 발생했습니다. 해결 방법으로는 스프링에서 지원하는 TransactionTemplate를 사용했습니다.

 

public class TransactionTemplate extends DefaultTransactionDefinition
       implements TransactionOperations, InitializingBean {

    /** Logger available to subclasses. */
    protected final Log logger = LogFactory.getLog(getClass());

    @Nullable
    private PlatformTransactionManager transactionManager;
    
    
    public <T> T execute(TransactionCallback<T> action) throws TransactionException {
    
    ...

- TransactionTemplate를 사용하는 주요 이유는 다음과 같습니다:

명시적인 코드: 특정 비즈니스 로직에서 트랜잭션 경계를 명시적으로 정의하고 싶을 때 사용합니다. 

동적 트랜잭션: 런타임에 트랜잭션 속성 (예: 전파 속성, 격리 수준 등)을 동적으로 변경하고자 할 때 사용할 수 있습니다.

 

- 사용 방법: 

TransactionTemplate의 execute 메서드를 사용하여 트랜잭션을 처리합니다. 이 메서드는 TransactionCallback 인터페이스를 인수로 받습니다. TransactionCallback 인터페이스는 함수형 인터페이스로 람다식으로 변경이 가능합니다.

 

변경된 코드

var updatedTemplate = tm.execute(status -> {
    var findTemplate = templateRepository.findById(savedTemplate.getId()).get();
    findTemplate.getContents(); // 객체초기화
    return findTemplate;
});
Assertions.assertAll(
        () -> assertThat(updatedTemplate.getTitle()).isEqualTo(title),
        () -> assertThat(updatedTemplate.getContents()).isEqualTo(questions));