본문 바로가기
공식문서

[Spring Docs] Transaction Management #2

by sangyunpark99 2025. 3. 18.
공식문서로 트랜잭션을 배워볼까요..?

 

 

이번글은 공식 문서에서 소개하는 Transaction Management에 대해 정리했습니다.

범위 : Declarative Transaction Management(<tx:advice/> Settings) ~ Using @ Transactional

 

<tx:advice/> Settings

 

This section summarizes the various transactional settings that you can specify by using the <tx:advice/> tag. The default <tx:advice/> settings are:

이 섹션은 tx:advice/ 태그를 사용하여 지정할 수 있는 다양한 트랜잭션 설정을 요약합니다. 기본 tx:advice/ 설정은 다음과 같습니다:

  • The propagation setting is REQUIRED.
  • The isolation level is DEFAULT.
  • The transaction is read-write.
  • The transaction timeout defaults to the default timeout of the underlying transaction system or none if timeouts are not supported.
  • Any RuntimeException triggers rollback, and any checked Exception does not.
  • 전파 설정은 REQUIRED입니다.
  • 격리 수준은 DEFAULT입니다.
  • 트랜잭션은 읽기-쓰기입니다.
  • 트랜잭션 타임아웃은 하위 트랜잭션 시스템의 기본 타임아웃 값을 기본값으로 사용하거나, 타임아웃이 지원되지 않을 경우 타임아웃이 없습니다.
  • 모든 RuntimeException은 롤백을 유발하며, 모든 체크 예외는 그렇지 않습니다.

 

You can change these default settings. The following table summarizes the various attributes of the <tx:method/> tags that are nested within <tx:advice/> and <tx:attributes/> tags:

 

이 기본 설정들은 변경할 수 있습니다. 다음 표는 tx:advice/tx:attributes/ 태그 내에 중첩된 tx:method/ 태그의 다양한 속성을 요약한 것입니다:

 

 

name
메서드 이름들로, 트랜잭션 속성이 연결될 메서드를 지정합니다. 와일드카드(*) 문자를 사용하여 여러 메서드(예: get, handle*, on*Event 등)에 동일한 트랜잭션 속성 설정을 적용할 수 있습니다.

 

propagation
트랜잭션 전파 동작을 지정합니다.

 

isolation
트랜잭션 격리 수준을 지정합니다. 이는 REQUIRED 또는 REQUIRES_NEW 전파 설정에만 적용됩니다.

 

timeout
트랜잭션 타임아웃(초 단위)을 지정합니다. REQUIRED 또는 REQUIRES_NEW 전파 설정에만 적용됩니다.

 

read-only
읽기-쓰기와 읽기 전용 트랜잭션을 구분합니다. REQUIRED 또는 REQUIRES_NEW 전파 설정에만 적용됩니다.

 

rollback-for
롤백을 유발하는 Exception 인스턴스들의 쉼표로 구분된 목록입니다. 예를 들어, com.foo.MyBusinessException,ServletException 등이 있습니다.

 

no-rollback-for
롤백을 유발하지 않는 Exception 인스턴스들의 쉼표로 구분된 목록입니다. 예를 들어, com.foo.MyBusinessException,ServletException 등이 있습니다.

 

 

Using @Transactional

In addition to the XML-based declarative approach to transaction configuration, you can use an annotation-based approach. Declaring transaction semantics directly in the Java source code puts the declarations much closer to the affected code. There is not much danger of undue coupling, because code that is meant to be used transactionally is almost always deployed that way anyway.

 

XML 기반 선언적 트랜잭션 구성 접근 방식 외에도, 어노테이션 기반 접근 방식을 사용할 수 있습니다.
Java 소스 코드에 직접 트랜잭션 의미를 선언함으로써, 선언들이 영향을 받는 코드에 훨씬 더 가까이 배치됩니다.
트랜잭션으로 사용되어야 하는 코드는 어쨌든 거의 항상 그렇게 배포되기 때문에, 과도한 결합의 위험은 그리 크지 않습니다.

"과도한 결합의 위험은 크지 않다"

트랜잭션 처리가 필요한 코드는 본래 트랜잭션 환경에서 실행되도록 설계되어 있기 때문에, 트랜잭션 관련 어노테이션을 코드에 직접 명시해도 문제가 발생할 위험이 적다는 뜻입니다. 즉, 트랜잭션이 필요한 코드를 실행할 때는 항상 트랜잭션 설정이 함께 적용되므로, 코드와 설정이 함께 있는 것이 오히려 자연스럽고, 유지보수에 큰 지장을 주지 않는다는 의미입니다.

 

 

The standard jakarta.transaction.Transactional annotation is also supported as a drop-in replacement to Spring’s own annotation. Please refer to the JTA documentation for more details.

표준 jakarta.transaction.Transactional 어노테이션은 Spring의 자체 어노테이션을 대체할 수 있는 drop-in으로도 지원됩니다. 자세한 내용은 JTA 문서를 참조하십시오.

"drop-in으로 지원된다"
기존에 Spring의 @Transactional 어노테이션을 사용하던 부분을 별도의 추가 설정이나 코드 변경 없이 표준 jakarta.transaction.Transactional 어노테이션으로 바로 대체할 수 있다는 의미입니다. 즉, 두 어노테이션이 기능적으로 동일하게 작동하여, 서로 쉽게 교체할 수 있다는 뜻입니다.

 

The ease-of-use afforded by the use of the @Transactional annotation is best illustrated with an example, which is explained in the text that follows. Consider the following class definition:

@Transactional 어노테이션 사용이 제공하는 사용 편의성은, 다음 텍스트에 설명된 예제로 가장 잘 설명됩니다. 다음 클래스 정의를 고려해 보세요:

// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

	@Override
	public Foo getFoo(String fooName) {
		// ...
	}

	@Override
	public Foo getFoo(String fooName, String barName) {
		// ...
	}

	@Override
	public void insertFoo(Foo foo) {
		// ...
	}

	@Override
	public void updateFoo(Foo foo) {
		// ...
	}
}

 

Used at the class level as above, the annotation indicates a default for all methods of the declaring class (as well as its subclasses). Alternatively, each method can be annotated individually. See method visibility for further details on which methods Spring considers transactional. Note that a class-level annotation does not apply to ancestor classes up the class hierarchy; in such a scenario, inherited methods need to be locally redeclared in order to participate in a subclass-level annotation.

 

위와 같이 클래스 레벨에서 사용될 경우, 해당 어노테이션은 선언한 클래스(및 그 하위 클래스)의 모든 메서드에 대한 기본 설정을 나타냅니다. 또는, 각 메서드에 대해 개별적으로 어노테이션을 적용할 수도 있습니다.
어떤 메서드를 Spring이 트랜잭션 대상으로 간주하는지에 대한 자세한 내용은 메서드 가시성을 참조하세요.
클래스 레벨 어노테이션은 클래스 계층 구조 상의 조상 클래스에는 적용되지 않음을 유의해야 합니다. 이러한 상황에서는 상속된 메서드가 하위 클래스 레벨의 어노테이션에 포함되기 위해 로컬에서 재선언되어야 한다.

 

 

When a POJO class such as the one above is defined as a bean in a Spring context, you can make the bean instance transactional through an @EnableTransactionManagement annotation in a @Configuration class. See the javadoc for full details.

 

위와 같은 POJO 클래스가 Spring 컨텍스트에서 빈으로 정의되면, @Configuration 클래스 내의 @EnableTransactionManagement 어노테이션을 통해 해당 빈 인스턴스를 트랜잭션 처리 대상으로 만들 수 있다. 자세한 내용은 javadoc을 참조하세요.

POJO는 "Plain Old Java Object"의 약자로, 특별한 제약 없이 단순한 자바 객체를 의미합니다.
특정 프레임워크나 라이브러리에 종속되지 않고 기본적인 자바 문법만을 사용하는 객체를 가리킵니다. 일반적으로 복잡한 상속 구조나 인터페이스 구현 없이 비즈니스 로직을 담은 단순한 클래스를 POJO라고 합니다.

 

In XML configuration, the <tx:annotation-driven/> tag provides similar convenience:

XML 구성에서는, tx:annotation-driven/ 태그가 유사한 편의성을 제공합니다:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/tx
		https://www.springframework.org/schema/tx/spring-tx.xsd
		http://www.springframework.org/schema/aop
		https://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- this is the service object that we want to make transactional -->
	<bean id="fooService" class="x.y.service.DefaultFooService"/>

	<!-- enable the configuration of transactional behavior based on annotations -->
	<!-- a TransactionManager is still required -->
	<tx:annotation-driven transaction-manager="txManager"/> (1)

	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- (this dependency is defined somewhere else) -->
		<property name="dataSource" ref="dataSource"/>
	</bean>

	<!-- other <bean/> definitions here -->

</beans>
 

(1) The line that makes the bean instance transactional.

빈 인스턴스를 트랜잭션 처리 대상으로 만드는 라인

 

You can omit the transaction-manager attribute in the <tx:annotation-driven/> tag if the bean name of the TransactionManager that you want to wire in has the name transactionManager. If the TransactionManager bean that you want to dependency-inject has any other name, you have to use the transaction-manager attribute, as in the preceding example.

 

연결하려는 TransactionManager의 빈 이름이 transactionManager인 경우, tx:annotation-driven/ 태그에서 transaction-manager 속성을 생략할 수 있습니다.
만약 의존성 주입하려는 TransactionManager 빈의 이름이 다른 경우, 앞의 예제처럼 transaction-manager 속성을 사용해야 합니다.

즉, TransactionManager 빈의 이름이 "transactionManager"라면, tx:annotation-driven/ 태그에서 transaction-manager 속성을 따로 지정하지 않아도 된다는 뜻입니다. 왜냐하면 Spring은 기본적으로 "transactionManager"라는 이름의 빈을 자동으로 찾기 때문입니다. 그러나 만약 연결하고자 하는 TransactionManager 빈의 이름이 다르다면, 해당 빈의 이름을 transaction-manager 속성에 명시해주어야 합니다.

 

 

Reactive transactional methods use reactive return types in contrast to imperative programming arrangements as the following listing shows:

아래의 나열된 예시가 보여주듯, 반응형 트랜잭션 메서드들은 명령형 프로그래밍 방식과 대조적으로 반응형 반환 타입을 사용합니다:

// the reactive service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

	@Override
	public Publisher<Foo> getFoo(String fooName) {
		// ...
	}

	@Override
	public Mono<Foo> getFoo(String fooName, String barName) {
		// ...
	}

	@Override
	public Mono<Void> insertFoo(Foo foo) {
		// ...
	}

	@Override
	public Mono<Void> updateFoo(Foo foo) {
		// ...
	}
}

 

Note that there are special considerations for the returned Publisher with regards to Reactive Streams cancellation signals. See the Cancel Signals section under "Using the TransactionalOperator" for more details.

 

반환된 Publisher에 관해 Reactive Streams 취소 신호와 관련된 특별한 고려 사항이 있다는 점을 유의하십시오. 자세한 내용은 "Using the TransactionalOperator" 하위의 Cancel Signals 섹션을 참조하십시오.

 

Method visibility and @Transactional in proxy mode

The @Transactional annotation is typically used on methods with public visibility. As of 6.0, protected or package-visible methods can also be made transactional for class-based proxies by default. Note that transactional methods in interface-based proxies must always be public and defined in the proxied interface. For both kinds of proxies, only external method calls coming in through the proxy are intercepted.

If you prefer consistent treatment of method visibility across the different kinds of proxies (which was the default up until 5.3), consider specifying publicMethodsOnly:

 

프록시 모드에서의 메소드 가시성과 @Transactional

@Transactional 어노테이션은 일반적으로 public 가시성을 가진 메소드에 사용된다. 6.0부터는, 클래스 기반 프록시에서 protected 또는 package-visible 메소드들도 기본적으로 트랜잭셔널로 만들 수 있다. 인터페이스 기반 프록시에서 트랜잭셔널 메소드는 항상 public이어야 하며, 프록시된 인터페이스에 정의되어 있어야 함을 유의하라. 두 종류의 프록시 모두에 대해, 프록시를 통해 들어오는 외부 메소드 호출만이 가로채진다.

서로 다른 종류의 프록시에서 메소드 가시성을 일관되게 처리하기를 원한다면 (이는 5.3까지의 기본값이었던 설정임), publicMethodsOnly를 지정하는 것을 고려하라:

/**
 * Register a custom AnnotationTransactionAttributeSource with the
 * publicMethodsOnly flag set to true to consistently ignore non-public methods.
 * @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
 */
@Bean
TransactionAttributeSource transactionAttributeSource() {
	return new AnnotationTransactionAttributeSource(true);
}
"프록시된 인터페이스에 정의되어 있어야 한다"는 것은, 인터페이스 기반 프록시를 사용할 때, 트랜잭션 처리를 적용하려는 메서드가 그 인터페이스에 선언되어 있어야 한다는 뜻입니다. 즉, Spring은 인터페이스를 기반으로 동적 프록시를 생성하여 트랜잭션 처리를 적용하는데, 이 프록시는 인터페이스에 정의된 메서드만 감싸서 호출을 가로채고 처리합니다. 만약 트랜잭션 적용 대상인 메서드가 인터페이스에 선언되어 있지 않다면, 프록시는 해당 메서드를 알 수 없으므로 트랜잭션 처리가 적용되지 않습니다.

 

You can apply the @Transactional annotation to an interface definition, a method on an interface, a class definition, or a method on a class. However, the mere presence of the @Transactional annotation is not enough to activate the transactional behavior. The @Transactional annotation is merely metadata that can be consumed by corresponding runtime infrastructure which uses that metadata to configure the appropriate beans with transactional behavior. In the preceding example, the <tx:annotation-driven/> element switches on actual transaction management at runtime.

인터페이스 정의, 인터페이스의 메서드, 클래스 정의 또는 클래스의 메서드에 @Transactional 어노테이션을 적용할 수 있습니다. 그러나 @Transactional 어노테이션이 단순히 존재한다고 해서 트랜잭션 동작이 활성화되는 것은 아닙니다. @Transactional 어노테이션은 단지 메타데이터일 뿐이며, 해당 메타데이터를 소비하여 적절한 빈들에 트랜잭션 동작을 구성하는 런타임 인프라에 의해 사용될 수 있습니다. 앞의 예제에서는 tx:annotation-driven/ 요소가 런타임에 실제 트랜잭션 관리를 활성화시킵니다.

 

The Spring team recommends that you annotate methods of concrete classes with the @Transactional annotation, rather than relying on annotated methods in interfaces, even if the latter does work for interface-based and target-class proxies as of 5.0. Since Java annotations are not inherited from interfaces, interface-declared annotations are still not recognized by the weaving infrastructure when using AspectJ mode, so the aspect does not get applied. As a consequence, your transaction annotations may be silently ignored: Your code might appear to "work" until you test a rollback scenario.

 

Spring 팀은, 후자의 경우(5.0부터 인터페이스 기반 및 타깃 클래스 프록시에서는 작동하더라도) 인터페이스에 어노테이션이 적용된 메서드에 의존하기보다는 구체 클래스의 메서드에 @Transactional 어노테이션을 적용할 것을 권장합니다.

Java 어노테이션은 인터페이스로부터 상속되지 않기 때문에, 인터페이스에 선언된 어노테이션은 AspectJ 모드를 사용할 때 위빙 인프라스트럭처에 의해 여전히 인식되지 않아 해당 aspect가 적용되지 않습니다.
그 결과, 트랜잭션 어노테이션이 아무런 경고 없이 무시될 수 있습니다: 롤백 시나리오를 테스트하기 전까지는 코드가 '작동하는' 것처럼 보일 수 있습니다.

 

Spring 5.0부터는 인터페이스에 선언된 @Transactional 어노테이션이 인터페이스 기반 프록시나 타깃 클래스 프록시 환경에서 어느 정도 작동할 수 있습니다.
즉, 인터페이스에 @Transactional을 붙이면, 해당 메서드를 호출할 때 트랜잭션이 적용될 가능성이 있습니다.
하지만, Java 자체의 특성상 어노테이션은 인터페이스에서 구현 클래스(실제 클래스로 구현된 객체)로 자동으로 상속되지 않습니다.
즉, 인터페이스에 @Transactional을 붙여도, 그 어노테이션 정보가 구현 클래스의 메서드에 자동으로 포함되지 않습니다.
이런 이유 때문에, 인터페이스에 @Transactional을 선언하더라도, 일부 환경(예를 들어 AspectJ 위빙 모드 등)에서는 그 정보가 제대로 인식되지 않아 트랜잭션 처리가 적용되지 않을 수 있습니다. 그래서 Spring 팀은 안정적으로 트랜잭션 처리가 이루어지도록 구체 클래스의 메서드에 직접 @Transactional을 붙일 것을 권장하는 것입니다.

간단히 말하면, 인터페이스에 @Transactional을 붙이는 것은 어느 정도 작동할 수 있지만, Java의 어노테이션 상속 특성 때문에 항상 안전하게 작동하는 것은 아니므로, 구현 클래스의 메서드에 직접 붙이는 편이 확실하다는 뜻입니다.

 

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that 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 even if the invoked method is marked with @Transactional. Also, the proxy must be fully initialized to provide the expected behavior, so you should not rely on this feature in your initialization code — for example, in a @PostConstruct method.

 

프록시 모드(기본값인 경우)에서는 프록시를 통해 들어오는 외부 메서드 호출만이 인터셉트됩니다. 이는 자기 호출(즉, 대상 객체 내부의 한 메서드가 대상 객체의 다른 메서드를 호출하는 경우)이, 호출된 메서드가 @Transactional로 표시되어 있더라도 런타임에서 실제 트랜잭션을 발생시키지 않는다는 것을 의미합니다. 또한, 프록시가 예상되는 동작을 제공하기 위해 완전히 초기화되어야 하므로, 초기화 코드—예를 들어 @PostConstruct 메서드—에서 이 기능에 의존해서는 안 됩니다.

내부에서 같은 객체의 다른 메서드를 호출하는 자가 호출은 프록시를 거치지 않기 때문에 @Transactional이 적용되지 않습니다.
 

Consider using AspectJ mode (see the mode attribute in the following table) if you expect self-invocations to be wrapped with transactions as well. In this case, there is no proxy in the first place. Instead, the target class is woven (that is, its byte code is modified) to support @Transactional runtime behavior on any kind of method.

자가 호출도 트랜잭션으로 감싸지길 기대한다면, 아래 표의 mode 속성을 참조하여 AspectJ 모드를 사용하는 것을 고려하세요. 이 경우, 처음부터 프록시가 존재하지 않습니다. 대신, 대상 클래스가 위빙됩니다.(즉, 그 바이트 코드가 수정된다) 모든 종류의 메서드에 대해 @Transactional 런타임 동작을 지원하기 위해서

만약 객체 내부에서 자기 호출할 때도 트랜잭션 처리가 적용되길 원한다면, 프록시 방식 대신 AspectJ 모드를 사용해 보세요. AspectJ 모드에서는 프록시를 만들지 않고, 클래스의 바이트 코드를 직접 수정해서 모든 메서드에 대해 @Transactional 기능이 적용되도록 합니다.
 

 

 

transactionManager
사용할 트랜잭션 관리자의 이름으로, 앞의 예제에서와 같이 트랜잭션 관리자의 이름이 transactionManager가 아닌 경우에만 필요합니다.

 

mode

기본 모드(proxy)는 주석이 붙은 빈들을 Spring의 AOP 프레임워크를 사용하여 프록시 처리합니다 (앞서 논의한 프록시 의미론을 따르며, 프록시를 통해 들어오는 메서드 호출에만 적용됨). 대안 모드(aspectj)는 대신에 영향을 받는 클래스들을 Spring의 AspectJ 트랜잭션 어드바이스로 위빙하여, 대상 클래스의 바이트 코드를 수정함으로써 모든 종류의 메서드 호출에 적용되도록 합니다. AspectJ 위빙은 classpath에 spring-aspects.jar가 있어야 하며, 로드 타임 위빙(또는 컴파일 타임 위빙)이 활성화되어 있어야 합니다. (로드 타임 위빙 설정 방법에 대한 자세한 내용은 Spring 구성 문서를 참조하십시오.)

Proxy 모드 (기본 모드): Spring은 @Transactional이 붙은 빈에 대해 AOP 프록시를 만듭니다.
이 프록시는 외부에서 빈의 메서드를 호출할 때만 동작합니다. 즉, 객체 내부에서 자기 자신을 호출하면 프록시가 개입하지 않습니다.

AspectJ 모드 (대안 모드): Spring은 AspectJ를 사용해 대상 클래스의 바이트 코드를 직접 수정합니다. 이 방식은 클래스의 모든 메서드 호출(내부 호출 포함)에 트랜잭션 처리를 적용할 수 있습니다. 다만, 이 기능을 사용하려면 spring-aspects.jar가 classpath에 있어야 하고, 로드 타임 위빙이나 컴파일 타임 위빙 설정이 필요합니다.

즉, Proxy 모드는 간단하지만 자기 호출에는 적용되지 않고, AspectJ 모드는 설정이 복잡하지만 모든 호출에 대해 트랜잭션 처리가 가능하다는 차이가 있습니다.
 

 

이 옵션들은 주로 스프링의 XML 구성 파일에서 tx:annotation-driven/ 태그의 속성으로 사용됩니다.

 

proxy-target-class

프록시 모드에만 적용됩니다. @Transactional 어노테이션이 적용된 클래스에 대해 생성되는 트랜잭션 프록시의 유형을 제어합니다. 만약 proxy-target-class 속성이 true로 설정되면, 클래스 기반의 프록시가 생성됩니다. proxy-target-class가 false이거나 해당 속성이 생략되면, 표준 JDK 인터페이스 기반의 프록시가 생성됩니다. (다양한 프록시 유형에 대한 자세한 검토는 Proxying Mechanisms을 참조하십시오.)

 

 

order

@Transactional 어노테이션이 적용된 빈에 적용되는 트랜잭션 어드바이스의 순서를 정의합니다. (AOP 어드바이스 순서와 관련된 규칙에 대한 자세한 정보는 Advice Ordering을 참조하십시오.) 지정된 순서가 없으면, AOP 하위 시스템이 어드바이스의 순서를 결정합니다.

 

 

The default advice mode for processing @Transactional annotations is proxy, which allows for interception of calls through the proxy only. Local calls within the same class cannot get intercepted that way. For a more advanced mode of interception, consider switching to aspectj mode in combination with compile-time or load-time weaving.

 

@Transactional 어노테이션을 처리하기 위한 기본 어드바이스 모드는 프록시이며, 이는 프록시를 통한 호출만 가로채도록 허용합니다. 동일 클래스 내의 로컬 호출은 그러한 방식으로 가로채질 수 없습니다. 보다 진보된 가로채기 방식을 원한다면, 컴파일 타임 또는 로드 타임 위빙과 결합하여 aspectj 모드로 전환하는 것을 고려하십시오.

 
 
The proxy-target-class attribute controls what type of transactional proxies are created for classes annotated with
the @Transactional annotation. If proxy-target-class is set to true, class-based proxies are created. If proxy-target-class is false or if the attribute is omitted, standard JDK interface-based proxies are created. (See Proxying Mechanisms for a discussion of the different proxy types.)
 
proxy-target-class 속성은 @Transactional 어노테이션이 적용된 클래스에 대해 생성되는 트랜잭션 프록시의 유형을 제어합니다. 만약 proxy-target-class가 true로 설정되면, 클래스 기반 프록시가 생성됩니다. proxy-target-class가 false이거나 속성이 생략되면, 표준 JDK 인터페이스 기반 프록시가 생성됩니다. (다양한 프록시 유형에 대한 논의는 Proxying Mechanisms을 참조하십시오.)

 

 

@EnableTransactionManagement and <tx:annotation-driven/> look for @Transactional only on beans in the same application context in which they are defined. This means that, if you put annotation-driven configuration in a WebApplicationContext for a DispatcherServlet, it checks for @Transactional beans only in your controllers and not in your services. See MVC for more information.

@EnableTransactionManagement와 tx:annotation-driven/ 해당 어노테이션이 정의된 동일한 애플리케이션 컨텍스트 내의 빈에서만 @Transactional을 찾습니다. 이는, 예를 들어 DispatcherServlet의 WebApplicationContext에 어노테이션 기반 구성을 배치하면, 컨트롤러에서만 @Transactional 빈을 검사하고 서비스에서는 검사하지 않는다는 것을 의미합니다. 자세한 내용은 MVC를 참조하십시오.

세 번째 문단의 의미는, @EnableTransactionManagement와 tx:annotation-driven/ 같은 트랜잭션 관리 설정은, 설정이 정의된 동일한 애플리케이션 컨텍스트 내에 있는 빈들만 대상으로 @Transactional 어노테이션을 찾아 처리한다는 것입니다. 예를 들어, 만약 DispatcherServlet의 WebApplicationContext에 트랜잭션 관련 설정을 넣는다면, 이 설정은 그 컨텍스트 내에 등록된 컨트롤러 빈들에 대해서만 @Transactional 어노테이션을 검사하고, 별도의 애플리케이션 컨텍스트에 등록된 서비스 빈들은 검사하지 않습니다. 즉, 트랜잭션 설정의 적용 범위가 설정이 정의된 컨텍스트에 한정된다는 점을 유의해야 합니다.
 

 

 

The most derived location takes precedence when evaluating the transactional settings for a method. In the case of the following example, the DefaultFooService class is annotated at the class level with the settings for a read-only transaction, but the @Transactional annotation on the updateFoo(Foo) method in the same class takes precedence over the transactional settings defined at the class level.

 

가장 상세한(하위) 위치가 메서드에 대한 트랜잭션 설정을 평가할 때 우선순위를 갖습니다. 아래 예제의 경우, DefaultFooService 클래스는 클래스 레벨에서 읽기 전용 트랜잭션 설정으로 어노테이션되어 있지만, 동일 클래스 내의 updateFoo(Foo) 메서드에 붙은 @Transactional 어노테이션이 클래스 레벨에서 정의된 트랜잭션 설정보다 우선합니다.

 

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

	public Foo getFoo(String fooName) {
		// ...
	}

	// these settings have precedence for this method
	@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
	public void updateFoo(Foo foo) {
		// ...
	}
}

 

@Transactional Settings

 

The @Transactional annotation is metadata that specifies that an interface, class, or method must have transactional semantics (for example, "start a brand new read-only transaction when this method is invoked, suspending any existing transaction"). The default @Transactional settings are as follows:

  • The propagation setting is PROPAGATION_REQUIRED.
  • The isolation level is ISOLATION_DEFAULT.
  • The transaction is read-write.
  • The transaction timeout defaults to the default timeout of the underlying transaction system, or to none if timeouts are not supported.
  • Any RuntimeException or Error triggers rollback, and any checked Exception does not.

You can change these default settings. The following table summarizes the various properties of the @Transactional annotation:

@Transactional 어노테이션은 인터페이스, 클래스 또는 메서드가 트랜잭션 의미(예: "이 메서드가 호출될 때 새로운 읽기 전용 트랜잭션을 시작하고, 기존의 트랜잭션은 일시 중지한다")를 가져야 함을 명시하는 메타데이터입니다. 기본 @Transactional 설정은 다음과 같습니다:

  • 전파 설정은 PROPAGATION_REQUIRED입니다.
  • 격리 수준은 ISOLATION_DEFAULT입니다.
  • 트랜잭션은 읽기-쓰기입니다.
  • 트랜잭션 타임아웃은 하위 트랜잭션 시스템의 기본 타임아웃을 기본값으로 사용하거나, 타임아웃이 지원되지 않을 경우 타임아웃이 없습니다.
  • 모든 RuntimeException 또는 Error는 롤백을 유발하며, 모든 체크 예외(Checked Exception)는 그렇지 않습니다.

이 기본 설정들은 변경할 수 있습니다. 다음 표는 @Transactional 어노테이션의 다양한 속성을 요약한 것입니다:

 

 

value
사용할 트랜잭션 관리자를 지정하는 선택적 한정자입니다.

 

transactionManager
value의 별칭입니다.

 

label
트랜잭션에 표현력 있는 설명을 추가하기 위한 문자열 라벨 배열입니다.
라벨은 트랜잭션 관리자에 의해 평가되어 실제 트랜잭션과 구현 특정 동작을 연관시킬 수 있습니다.

 

propagation
선택적 전파 설정입니다.

 

isolation
선택적 격리 수준입니다. REQUIRED 또는 REQUIRES_NEW 전파 값에만 적용됩니다.

 

timeout
선택적 트랜잭션 타임아웃입니다(초 단위). REQUIRED 또는 REQUIRES_NEW 전파 값에만 적용됩니다.

 

timeoutString
타임아웃을 초 단위의 문자열 값(예: 플레이스홀더로서)으로 지정하는 대안입니다.

 

readOnly
읽기-쓰기와 읽기 전용 트랜잭션을 구분합니다. REQUIRED 또는 REQUIRES_NEW 값에만 적용됩니다.

 

rollbackFor
롤백을 유발해야 하는 예외 유형들의 선택적 배열입니다.

 

rollbackForClassName
롤백을 유발해야 하는 예외 이름 패턴들의 선택적 배열입니다.

 

noRollbackFor
롤백을 유발하지 않아야 하는 예외 유형들의 선택적 배열입니다.

 

noRollbackForClassName
롤백을 유발하지 않아야 하는 예외 이름 패턴들의 선택적 배열입니다.

 

 

As of 6.2, you can globally change the default rollback behavior – for example, through @EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS), leading to a rollback for all exceptions raised within a transaction, including any checked exception. For further customizations, AnnotationTransactionAttributeSource provides an addDefaultRollbackRule(RollbackRuleAttribute) method for custom default rules.

 

6.2부터, 예를 들어 @EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS)를 통해 전역적으로 기본 롤백 동작을 변경할 수 있으며, 이 경우 트랜잭션 내에서 발생한 모든 예외(체크 예외 포함)에 대해 롤백이 이루어집니다.
추가적인 커스터마이징을 위해, AnnotationTransactionAttributeSource는 사용자 정의 기본 규칙을 위한 addDefaultRollbackRule(RollbackRuleAttribute) 메서드를 제공합니다.

 

Note that transaction-specific rollback rules override the default behavior but retain the chosen default for unspecified exceptions. This is the case for Spring’s @Transactional as well as JTA’s jakarta.transaction.Transactional annotation.

 

트랜잭션별 롤백 규칙은 기본 동작을 재정의하지만, 지정되지 않은 예외에 대해서는 선택한 기본값을 유지한다는 점에 유의하십시오.
이는 Spring의 @Transactional과 JTA의 jakarta.transaction.Transactional 어노테이션 모두에 해당합니다.

 

Unless you rely on EJB-style business exceptions with commit behavior, it is advisable to switch to ALL_EXCEPTIONS for consistent rollback semantics even in case of a (potentially accidental) checked exception. Also, it is advisable to make that switch for Kotlin-based applications where there is no enforcement of checked exceptions at all.

 

EJB 스타일의 비즈니스 예외(commit 동작을 가진)를 사용하지 않는 한, (우발적인) 체크 예외가 발생하더라도 일관된 롤백 의미론을 위해 ALL_EXCEPTIONS로 전환하는 것이 권장됩니다.
또한, 체크 예외가 전혀 강제되지 않는 Kotlin 기반 애플리케이션의 경우에도 해당 전환을 하는 것이 권장됩니다.

이 문장은 기본적으로 스프링이 트랜잭션 롤백을 처리할 때, 기본 설정으로는 RuntimeException이나 Error와 같은 Unchecked 예외만 롤백하고, 체크 예외는 롤백하지 않도록 되어 있다는 점을 말합니다.
그런데 만약 애플리케이션에서 "EJB 스타일의 비즈니스 예외"—즉, 예외가 발생해도 커밋(commit) 동작을 하도록 설계된 예외 처리 방식을 사용하지 않는다면, 우발적인(의도하지 않은) 체크 예외가 발생하더라도 트랜잭션이 롤백되도록 ALL_EXCEPTIONS 옵션으로 전환하는 것이 좋다는 의미입니다.
또한 Kotlin은 체크 예외를 강제하지 않기 때문에, Kotlin 기반 애플리케이션에서는 예외 발생 시 항상 롤백되도록 ALL_EXCEPTIONS 옵션을 사용하는 것이 일관된 동작을 보장할 수 있어서 추천된다는 뜻입니다.
 

 

Currently, you cannot have explicit control over the name of a transaction, where 'name' means the transaction name that appears in a transaction monitor and in logging output. For declarative transactions, the transaction name is always the fully-qualified class name of the transactionally advised class + . + the method name. For example, if the handlePayment(..) method of the BusinessService class started a transaction, the name of the transaction would be com.example.BusinessService.handlePayment.

 

현재, 트랜잭션 모니터와 로깅 출력에 나타나는 트랜잭션 이름을 명시적으로 제어할 수 없습니다. 선언적 트랜잭션의 경우, 트랜잭션 이름은 항상 트랜잭션 어드바이스가 적용된 클래스의 완전한 클래스 이름 + "." + 메서드 이름으로 지정됩니다. 예를 들어, BusinessService 클래스의 handlePayment(..) 메서드가 트랜잭션을 시작했다면, 트랜잭션의 이름은 com.example.BusinessService.handlePayment가 됩니다.

 

Multiple Transaction Managers with @Transactional

Most Spring applications need only a single transaction manager, but there may be situations where you want multiple independent transaction managers in a single application. You can use the value or transactionManager attribute of the @Transactional annotation to optionally specify the identity of the TransactionManager to be used. This can either be the bean name or the qualifier value of the transaction manager bean. For example, using the qualifier notation, you can combine the following Java code with the following transaction manager bean declarations in the application context:

 

대부분의 Spring 애플리케이션은 단일 트랜잭션 관리자로 충분하지만, 하나의 애플리케이션에서 여러 개의 독립적인 트랜잭션 관리자가 필요한 상황도 있을 수 있습니다. @Transactional 어노테이션의 value 또는 transactionManager 속성을 사용하여 사용할 TransactionManager의 식별자를 선택적으로 지정할 수 있습니다. 이는 트랜잭션 관리자 빈의 이름이 될 수도 있고, 트랜잭션 관리자 빈의 qualifier 값이 될 수도 있습니다. 예를 들어, qualifier 표기를 사용하여 다음의 Java 코드와 애플리케이션 컨텍스트에 선언된 다음의 트랜잭션 관리자 빈 선언을 결합할 수 있습니다.

public class TransactionalService {

	@Transactional("order")
	public void setSomething(String name) { ... }

	@Transactional("account")
	public void doSomething() { ... }

	@Transactional("reactive-account")
	public Mono<Void> doSomethingReactive() { ... }
}

 

The following listing shows the bean declarations:

다음 나열은 빈 선언들을 보여줍니다:

<tx:annotation-driven/>

	<bean id="transactionManager1" class="org.springframework.jdbc.support.JdbcTransactionManager">
		...
		<qualifier value="order"/>
	</bean>

	<bean id="transactionManager2" class="org.springframework.jdbc.support.JdbcTransactionManager">
		...
		<qualifier value="account"/>
	</bean>

	<bean id="transactionManager3" class="org.springframework.data.r2dbc.connection.R2dbcTransactionManager">
		...
		<qualifier value="reactive-account"/>
	</bean>

 

In this case, the individual methods on TransactionalService run under separate transaction managers, differentiated by the order, account, and reactive-account qualifiers. The default <tx:annotation-driven> target bean name, transactionManager, is still used if no specifically qualified TransactionManager bean is found.

 

이 경우, TransactionalService의 개별 메서드들은 order, account, 및 reactive-account 한정자에 따라 구분되는 별도의 트랜잭션 관리자 하에서 실행됩니다. 특별히 한정된 TransactionManager 빈이 발견되지 않으면, 기본 tx:annotation-driven 대상 빈 이름인 transactionManager가 여전히 사용됩니다.

 

If all transactional methods on the same class share the same qualifier, consider declaring a type-level org.springframework.beans.factory.annotation.Qualifier annotation instead. If its value matches the qualifier value (or bean name) of a specific transaction manager, that transaction manager is going to be used for transaction definitions without a specific qualifier on @Transactional itself.

 

모든 동일 클래스 내의 트랜잭션 메서드가 동일한 한정자를 공유한다면, 대신에 클래스 수준의 org.springframework.beans.factory.annotation.Qualifier 어노테이션을 선언하는 것을 고려하십시오. 만약 그 값이 특정 트랜잭션 관리자의 한정자 값(또는 빈 이름)과 일치하면, @Transactional 자체에 특정 한정자가 없는 트랜잭션 정의에 대해 해당 트랜잭션 관리자가 사용됩니다.

 

Such a type-level qualifier can be declared on the concrete class, applying to transaction definitions from a base class as well. This effectively overrides the default transaction manager choice for any unqualified base class methods.

 

이와 같은 클래스 수준의 한정자는 구체 클래스에 선언할 수 있으며, 기본 클래스의 트랜잭션 정의에도 적용됩니다. 이는 본질적으로 한정자가 없는 기본 클래스 메서드에 대해 기본 트랜잭션 관리자 선택을 재정의합니다.

 

Last but not least, such a type-level bean qualifier can serve multiple purposes, for example, with a value of "order" it can be used for autowiring purposes (identifying the order repository) as well as transaction manager selection, as long as the target beans for autowiring as well as the associated transaction manager definitions declare the same qualifier value. Such a qualifier value only needs to be unique within a set of type-matching beans, not having to serve as an ID.

 

마지막으로, 이러한 클래스 수준의 빈 한정자는 여러 용도로 사용될 수 있습니다. 예를 들어, 값이 "order"인 경우, 이는 자동 연결(autowiring) 목적(예: order repository 식별)뿐 아니라, 대상 자동 연결 빈들과 관련된 트랜잭션 관리자 정의들이 동일한 한정자 값을 선언하는 한, 트랜잭션 관리자 선택에도 사용될 수 있습니다. 이러한 한정자 값은 ID 역할을 할 필요 없이, 타입이 일치하는 빈 집합 내에서만 고유하면 됩니다.

쉽게 말하면, 만약 한 클래스의 모든 트랜잭션 메서드가 같은 “태그(한정자)”를 사용한다면, 클래스에 한 번만 @Qualifier("order")와 같이 선언해두는 것이 좋다는 뜻입니다.

이렇게 하면, 해당 클래스의 모든 트랜잭션 메서드는 따로 각각 지정하지 않아도 "order"라는 한정자에 맞는 트랜잭션 관리자를 사용하게 됩니다. 또한, 이 한정자는 자동 주입(autowiring) 시에도 같은 역할을 하여, 예를 들어 "order"라는 값으로 지정된 리포지토리나 다른 빈을 연결할 때도 유용합니다.

즉, 클래스 전체에 같은 한정자를 설정해두면, 트랜잭션 관리자 선택과 자동 주입을 한 번에 관리할 수 있어 편리하다는 의미입니다.
 
 

Custom Composed Annotations

If you find you repeatedly use the same attributes with @Transactional on many different methods, Spring’s meta-annotation support lets you define custom composed annotations for your specific use cases. For example, consider the following annotation definitions:

만약 여러 다른 메서드에서 @Transactional과 함께 같은 속성들을 반복해서 사용한다면, Spring의 메타-어노테이션 지원을 통해 특정 사용 사례에 맞는 사용자 정의 복합 어노테이션을 정의할 수 있습니다. 예를 들어, 다음의 어노테이션 정의를 고려해 보십시오:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "account", label = "retryable")
public @interface AccountTx {
}
 

The preceding annotations let us write the example from the previous section as follows:

앞서 언급된 어노테이션들을 사용하여, 이전 섹션의 예제를 다음과 같이 작성할 수 있습니다:

public class TransactionalService {

	@OrderTx
	public void setSomething(String name) {
		// ...
	}

	@AccountTx
	public void doSomething() {
		// ...
	}
}

 

In the preceding example, we used the syntax to define the transaction manager qualifier and transactional labels, but we could also have included propagation behavior, rollback rules, timeouts, and other features.

 

앞선 예제에서는 트랜잭션 관리자 한정자와 트랜잭션 라벨을 정의하는 구문을 사용했지만, 전파 동작, 롤백 규칙, 타임아웃 및 기타 기능들을 포함시킬 수도 있었습니다.

 

 

정리

  • Declarative Transaction Management 기본 설정:
    • 전파(propagation): 기본값은 REQUIRED
    • 격리(isolation): 기본값은 DEFAULT
    • 트랜잭션 유형: 기본적으로 읽기-쓰기
    • 타임아웃(timeout): 하위 트랜잭션 시스템의 기본 타임아웃 사용 (또는 타임아웃 미지원 시 없음)
    • 롤백 규칙: RuntimeException 또는 Error 발생 시 롤백, 체크 예외는 롤백하지 않음
  • 트랜잭션 구성 방식:
    • XML 구성: <tx:annotation-driven/> 태그를 사용해 트랜잭션 관리 활성화
    • 어노테이션 기반 구성: @Transactional을 코드에 직접 선언하여 트랜잭션 경계를 설정 (코드와 설정이 가까워 유지보수에 용이함)
  • 프록시 모드 vs. AspectJ 모드:
    • 프록시 모드 (기본값):
      • 외부에서 프록시를 통해 들어오는 메서드 호출에만 트랜잭션이 적용됨
      • 동일 객체 내부의 자기 호출은 트랜잭션이 적용되지 않음
    • AspectJ 모드:
      • 클래스의 바이트 코드를 위빙하여, 내부 호출까지 포함한 모든 메서드 호출에 트랜잭션 적용 가능
  • 트랜잭션 관리자 지정:
    • 기본적으로 트랜잭션 관리자의 이름이 transactionManager이면 별도 지정 불필요
    • 여러 개의 트랜잭션 관리자가 필요한 경우, @Transactional의 value 또는 transactionManager 속성(혹은 클래스 수준의 @Qualifier)을 사용해 구분
  • Custom Composed Annotations:
    • 동일한 트랜잭션 속성을 여러 메서드에 반복해서 사용한다면, 메타-어노테이션 지원을 이용해 사용자 정의 복합 어노테이션을 만들어 중복을 줄일 수 있음

'공식문서' 카테고리의 다른 글

[Spring Docs] Programmatic Transaction Management #4  (0) 2025.03.20
[Spring Docs] Transaction Management #3  (0) 2025.03.19
[Spring Docs] DispatcherServlet #3  (0) 2025.03.17
Garbage Collector #2  (0) 2025.03.17
[Spring Docs] DispatchServlet #1  (0) 2025.03.15