본문 바로가기

경험과 지식

예외 TransactionRequiredException: no transaction is in progress, Executing an update/delete query 해결방안

TransactionRequiredException 예외 메시지로 두가지를 경험해보았습니다.
no transaction is in progress 와 Executing an update/delete query 입니다.
발생하는 이유에 대해서는 잘 정리된 글들이 많아서, 각 메시지별 해결방법 위주로 작성해보았습니다.

 

@Transactional 어노테이션 누락 확인

트랜잭션으로 관리할 메서드에 종종 어노테이션을 누락한 적이 있습니다.

//@Transactional
public void update() throws Exception { //... }

 

트랜잭션 메서드 접근제어자 / 불변 키워드 확인

@Transactional 은 AOP로 구현되는데, 스프링 부트 2.0 이후부터는 기본적으로 CGLib이란 라이브러리를 활용하여 AOP를 구현합니다.

CGLib로 AOP를 구현하려면, 해당 메서드는 외부 접근가능해야하고 상속 가능해야합니다. (final 클래스, final 메서드 안 됨)

IDE 에서 에러를 표기해줄 수는 있지만, 컴파일은 정상적으로 됩니다. 런타임에러 주의해야겠습니다.

//public 이어야 함, final 없어야 함
@Transactional
public /*final*/ void update(){ //... }

 

내부 메서드로 호출하고 있지는 않는지 확인

접근제어자가 public이어도 같은 클래스 내의 메서드로 호출하면 안됩니다.

IDE에 따라 아래와 같은 경고 메시지가 나오며 런타임에서 에러가 발생합니다.

@Transactional self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime
    public void outer() {
        inner();
    }

    @Transactional
    public void inner() { //...정상 동작하지 않음   }

 

(참고) outer 메서드에 @Transactional를 선언한다면 정상 동작합니다. inner()의 @Transactional은 아무 의미 없습니다.

no transaction is in progress

JPA @Lock 어노테이션으로 명시적인 락을 획득하려는 경우, 호출 당시 활성 상태의 트랜잭션이 존재해야합니다. 이 메서드를 호출한 쪽에서 트랜잭션이 제대로 걸려있는지 확인해야합니다.

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.stereotype.Repository;
import javax.persistence.LockModeType;

@Repository
public interface MemberRepository extends JpaRepository<MemberEntity, String> {
	@Lock(value = LockModeType.PESSIMISTIC_WRITE)
	Optional<MemberEntity> findBySearchKeyword(String searchKeyword);
}

 

Executing an update/delete query

@Modifying 과 같이 JPQL, native query 로 레코드 업데이트나 삭제를 할 경우, 호출 당시 활성 상태의 트랜잭션이 존재해야합니다. 

이 메서드를 호출한 쪽에서 트랜잭션이 제대로 걸려있는지 확인해야합니다.

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface MemberRepository extends JpaRepository<MemberEntity, String> {
    @Modifying
    @Query(value = "update ... set ... where id = :id", nativeQuery = true)
    void update(String id);
}

(참고1) 정확히는... 조건절에 파라미터가 있는 경우에만 해당 예외가 발생하였습니다. (그럴일은 없겠지만) 'update member set age = 29' 처럼 아무 조건없는 네이티브 쿼리 요청에서는 정상적으로 업데이트 되었습니다.

 

(참고2) 이 주제와 상관 없지만, dirty check를 활용한 엔티티 업데이트에서도 @Transaction 으로 관리되는지에 따라 동작이 달라집니다. 이부분도 같이 보면 좋을 것 같습니다.

 

회고

편리할수록 추상적인 것 같습니다. 여러 라이브러리들과 프레임워크는 편리한 개발을 도와주지만 역설적으로 (잘 모르고 사용할 경우) 개발자의 의도대로 동작하지 않을 가능성이 많습니다. 잘 모르고 사용하는 일이 없도록 주의하도록 하겠습니다.