Post

Spring @Transactional

Spring 트랜잭션을 올바르게 구성하는 방법과 @Transactional을 잘 사용하는 방법

Spring @Transactional

1. 개요


Spring 트랜잭션을 올바르게 구성하는 방법과 @Transactional 을 잘 사용하는 방법 및 잘못된 사용 방법에 대해 알아보자.


2. 트랜잭션 설정


@Configuration 클래스에 @EnableTransactionManagement 를 사용하여 트랜잭션을 활성화 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
@EnableTransactionManagement
class PersistenceJPAConfig {
    
    @Bean
    fun entityManagerFactory(): LocalContainerEntityManagerFactoryBean {
        // ...
    }
    
    @Bean
    fun transactionManager(): PlatformTransactionManager {
        val transactionManager = JpaTransactionManager()
        transactionManager.setEntityManager(entityManagerFactory().getObject())
        return transactionManager
    }
}
  • spring-data-* 또는 spring-tx 의존이 있는 경우에는 기본적으로 트랜잭션이 활성화된다.


3. @Transactional 어노테이션


클래스나 메서드에 @Transactional 어노테이션을 정의할 수 있다.

1
2
3
4
5
@Service
@Transactional(propagation = Propagation.REQUIRED)
class FooService {
	// ...
}

KotlinCopyMore options

  • 다음과 같은 설정도 지원한다.
    • Propagation Type : 전파 유형
      • REQUIRED : 기본 값. 현재 트랜잭션이 존재하면 그 트랜잭션에 참여, 존재하지 않으면 새로운 트랜잭션을 시작
      • REQUIRES_NEW : 항상 새로운 트랜잭션을 시작. 현재 트랜잭션이 존재하면 일시 중단.
      • SUPPORTS : 현재 트랜잭션이 존재하면 그 트랜잭션에 참여, 존재하지 않으면 트랜잭션 없이 실행
      • NOT_SUPPORTS : 트랜잭션이 존재하면 일시 중단하고 트랜잭션 없이 실행
      • MANDATORY : 반드시 트랜잭션 내에서 실행되어야 함. 트랜잭션이 없으면 예외
      • NEVER : 트랜잭션이 없어야 실행. 트랜잭션이 존재하면 예외
      • NESTED : 현재 트랜잭션이 존재하면 중첩된 트랜잭션을 시작, 존재하지 않으면 새로운 트랜잭션을 시작.
    • Isolation Level : 격리 수준
      • DEFAULT : 데이터베이스의 격리 수준을 따름
      • READ_UNCOMMITTED : 다른 트랜잭션에서 커밋되지 않은 변경 내용도 읽음
      • READ_COMMITTED : 다른 트랜잭션에서 커밋된 데이터만 읽음
      • REPEATABLE_READ : 트랜잭션 동안 읽은 데이터가 변경되지 않도록 보장
      • SERIALIZABLE : 가능 높은 격리 수준으로, 완벽한 읽기 일관성 보장
    • Timeout : 시간 초과
    • readOnly : 읽기 전용
    • Rollback : 롤백 규칙


4. 잘못된 사용


4.1. 트랜잭션과 프록시


Spring은 @Transactional 이 붙은 모든 클래스에 대한 프록시를 생성한다. Spring은 프록시를 활용해 트랜잭션을 시작하고 커밋하기 위해 메서드 실행 전후에 트랜잭션 로직을 실행한다.

위와 같은 특성으로 인해 @Transactional 을 사용할 때 두 가지의 주의할 점이 있다.

첫 번째는 클래스 내에서 자기 자신의 메서드를 호출하는 경우에는 @Transactional 이 정상 동작하지 않는 것이다. @Transactional 이 붙은 빈은 트랜잭션 관리를 위해 Spring이 자동으로 생성하는 프록시 객체를 통해 호출되는데 내부 호출은 프록시를 거치지 않기 때문에 트랜잭션이 동작하지 않는 것이다.

즉, 다음과 같이 호출할 경우 @Transactional 이 동작하지 않게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TransactionalService {

    @Transactional
    public void methodA() {
        // Do something that requires a transaction
        System.out.println("methodA - Transaction started");
        methodB(); // Self-invocation
    }

    @Transactional
    public void methodB() {
        // Do something else that requires a transaction
        System.out.println("methodB - Transaction started");
    }
}

두 번째로 주의할 점은 public 메서드에만 @Transactional 을 붙어야 한다는 것이다. 다른 가시성에 대해서는 프록시가 동작하지 않으므로 @Transactional 이 무시된다.


4.2. Isolation Level 변경


다음과 같이 트랜잭션의 isolation level을 변경할 수 있다.

1
@Transactional(isolation = Isolation.SERIALIZABLE)

이는 Spring 4.1에서 도입되어 이전 버전에서는 예외가 발생한다


4.3. Read-Only 트랜잭션


다음과 같이 트랜잭션에 Read-Only를 설정할 수 있다.

1
@Transactional(readOnly = true)
  • 주의할 점은 readOnly가 true로 설정되어 있어도 삽입이나 업데이터가 발생하지 않을 것이라고 확신할 수 없다는 것이다. 이는 구현체에 따라 다르다.

readOnly 는 트랜잭션 내부에서만 동작하므로 트랜잭션 컨텍스트 외부에서 작업이 발생하면 readOnly가 무시된다.

1
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)

이처럼 트랜잭션 컨텍스트가 아닐 경우에는 readOnly가 무시될 수 있다.


4.4. 트랜잭션 로깅


트랜잭션 관련 문제를 디버깅 할 때 유용한 방법은 트랜잭션 패키지에 대한 로깅을 수정하는 것이다.

패키지는 “org.springframework.transaction” 이며, TRACE 로 로깅 레벨을 설정하면 된다.


5. 동작 원리


Spring Transaction Management: @Transactional In-Depth


Reference


Transactions with Spring and JPA | Baeldung

This post is licensed under CC BY 4.0 by the author.