본문 바로가기
공식문서

[Spring Docs] Programmatic Transaction Management #4

by sangyunpark99 2025. 3. 20.

 

선언적 트랜잭션이 아닌,
프로그래밍 방식의 트랜잭션은 어떻게 관리할까요?

 

 

이번글은 Programmatic Transaction Management에 대해 정리했습니다.

 

Programmatic Transaction Management

프로그램 방식의 트랜잭션 관리

 

The Spring Framework provides two means of programmatic transaction management, by using:

  • The TransactionTemplate or TransactionalOperator.
  • A TransactionManager implementation directly.

The Spring team generally recommends the TransactionTemplate for programmatic transaction management in imperative flows and TransactionalOperator for reactive code. The second approach is similar to using the JTA UserTransaction API, although exception handling is less cumbersome.

 

Spring Framework는 다음 두 가지 방법으로 프로그램 방식의 트랜잭션 관리를 제공합니다:

  • TransactionTemplate 또는 TransactionalOperator 사용
  • TransactionManager 구현체를 직접 사용하는 방법

Spring 팀은 일반적으로 명령형 흐름(imperative flows)에서의 프로그램 방식의 트랜잭션 관리를 위해 TransactionTemplate을, 반응형 코드(reactive code)에서는 TransactionalOperator를 권장합니다. 두 번째 접근 방식은 JTA UserTransaction API를 사용하는 것과 유사하지만, 예외 처리(exception handling)는 덜 번거롭습니다.

 

Using the TransactionTemplate

TransactionTemplate 사용

The TransactionTemplate adopts the same approach as other Spring templates, such as the JdbcTemplate. It uses a callback approach (to free application code from having to do the boilerplate acquisition and release transactional resources) and results in code that is intention driven, in that your code focuses solely on what you want to do.

TransactionTemplate은 JdbcTemplate과 같은 다른 Spring 템플릿과 동일한 방식을 채택합니다. 이는 애플리케이션 코드가 반복적인 트랜잭션 리소스 획득 및 해제 작업을 처리하지 않아도 되도록 콜백 방식을 사용하며, 결과적으로 코드가 오로지 수행하고자 하는 작업에만 집중할 수 있는 의도 중심의 코드가 됩니다.

 

콜백 방식이란?
콜백(callback) 방식은, 어떤 함수나 메서드에 "실행할 코드 조각"을 인자로 전달하고, 해당 함수가 내부적으로 이 전달받은 코드를 필요할 때 호출하는 프로그래밍 패턴입니다. 즉, 반복되거나 공통적으로 처리되는 로직을 템플릿 메서드 같은 곳에 위임하고, 그 안에서 특정 작업만 사용자 정의 코드로 전달하여 실행하도록 하는 방식입니다. 이를 통해, 리소스 획득 및 해제와 같은 반복적인 작업은 템플릿 코드에 맡기고, 실제 비즈니스 로직은 콜백으로 전달하여 작성할 수 있게 됩니다.
 
As the examples that follow show, using the TransactionTemplate absolutely couples you to Spring’s transaction infrastructure and APIs. Whether or not programmatic transaction management is suitable for your development needs is a decision that you have to make yourself.

다음에 나오는 예제들이 보여주듯, TransactionTemplate을 사용하면 반드시 Spring의 트랜잭션 인프라와 API에 결합됩니다. 프로그램 방식의 트랜잭션 관리가 개발 요구에 적합한지 여부는 여러분이 직접 결정해야 하는 문제입니다.

 

"인프라에 결합된다"는 표현은 TransactionTemplate을 사용함으로써 해당 코드가 Spring의 트랜잭션 인프라와 API에 의존하게 된다는 의미입니다. 이를 통해 Spring의 제공하는 기능을 활용할 수 있지만, 동시에 Spring의 트랜잭션 관리 방식에 종속된다는 점을 고려해야 합니다.
 

Application code that must run in a transactional context and that explicitly uses the TransactionTemplate resembles the next example. You, as an application developer, can write a TransactionCallback implementation (typically expressed as an anonymous inner class) that contains the code that you need to run in the context of a transaction. You can then pass an instance of your custom TransactionCallback to the execute(..) method exposed on the TransactionTemplate. The following example shows how to do so:

트랜잭션 컨텍스트 내에서 실행되어야 하고 명시적으로 TransactionTemplate을 사용하는 애플리케이션 코드는 다음 예제와 유사합니다. 애플리케이션 개발자로서, 여러분은 트랜잭션 컨텍스트 내에서 실행되어야 하는 코드를 포함하는 TransactionCallback 구현체(일반적으로 익명 내부 클래스로 표현됨)를 작성할 수 있습니다. 이후, 여러분은 커스텀 TransactionCallback의 인스턴스를TransactionTemplate에 노출된 execute(..) 메서드에 전달할 수 있습니다. 다음 예제는 그 방법을 보여줍니다.

 

public class SimpleService implements Service {

	// single TransactionTemplate shared amongst all methods in this instance
	private final TransactionTemplate transactionTemplate;

	// use constructor-injection to supply the PlatformTransactionManager
	public SimpleService(PlatformTransactionManager transactionManager) {
		this.transactionTemplate = new TransactionTemplate(transactionManager);
	}

	public Object someServiceMethod() {
		return transactionTemplate.execute(new TransactionCallback() {
			// the code in this method runs in a transactional context
			public Object doInTransaction(TransactionStatus status) {
				updateOperation1();
				return resultOfUpdateOperation2();
			}
		});
	}
}

 

If there is no return value, you can use the convenient TransactionCallbackWithoutResult class with an anonymous class, as follows:

만약 반환 값이 없다면, 다음과 같이 익명 클래스를 사용하여 편리한 TransactionCallbackWithoutResult 클래스를 사용할 수 있습니다.

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
	protected void doInTransactionWithoutResult(TransactionStatus status) {
		updateOperation1();
		updateOperation2();
	}
});

 

Code within the callback can roll the transaction back by calling the setRollbackOnly() method on the supplied TransactionStatus object, as follows:

콜백 내의 코드는 제공된 TransactionStatus 객체에 대해 setRollbackOnly() 메서드를 호출함으로써 트랜잭션을 롤백할 수 있습니다, 

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

	protected void doInTransactionWithoutResult(TransactionStatus status) {
		try {
			updateOperation1();
			updateOperation2();
		} catch (SomeBusinessException ex) {
			status.setRollbackOnly();
		}
	}
});

 

Specifying Transaction Settings

트랜잭션 설정 지정

 

You can specify transaction settings (such as the propagation mode, the isolation level, the timeout, and so forth) on the TransactionTemplate either programmatically or in configuration. By default, TransactionTemplate instances have the default transactional settings. The following example shows the programmatic customization of the transactional settings for a specific TransactionTemplate:

 

전파 모드, 격리 수준, 타임아웃 등과 같은 트랜잭션 설정을 TransactionTemplate에 대해 프로그래밍 방식이나 설정에서 지정할 수 있습니다. 기본적으로, TransactionTemplate 인스턴스는 기본 트랜잭션 설정을 가집니다. 다음 예제는 특정 TransactionTemplate에 대한 트랜잭션 설정의 프로그래밍적 커스터마이징을 보여줍니다.

public class SimpleService implements Service {

	private final TransactionTemplate transactionTemplate;

	public SimpleService(PlatformTransactionManager transactionManager) {
		this.transactionTemplate = new TransactionTemplate(transactionManager);

		// the transaction settings can be set here explicitly if so desired
		this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
		this.transactionTemplate.setTimeout(30); // 30 seconds
		// and so forth...
	}
}
The following example defines a TransactionTemplate with some custom transactional settings by using Spring XML configuration:
 
다음은 Spring XML 설정을 사용하여 일부 사용자 정의 트랜잭션 설정을 가진 TransactionTemplate을 정의하는 예제입니다:
<bean id="sharedTransactionTemplate"
		class="org.springframework.transaction.support.TransactionTemplate">
	<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
	<property name="timeout" value="30"/>
</bean>

 

You can then inject the sharedTransactionTemplate into as many services as are required.

Finally, instances of the TransactionTemplate class are thread-safe, in that instances do not maintain any conversational state. TransactionTemplate instances do, however, maintain configuration state. So, while a number of classes may share a single instance of a TransactionTemplate, if a class needs to use a TransactionTemplate with different settings (for example, a different isolation level), you need to create two distinct TransactionTemplate instances.

 

필요한 만큼 많은 서비스에 sharedTransactionTemplate을 주입할 수 있습니다.

마지막으로, TransactionTemplate 클래스의 인스턴스들은 대화 상태(conversational state)를 유지하지 않으므로 스레드 안전(thread-safe)합니다. 그러나 TransactionTemplate 인스턴스들은 설정 상태(configuration state)는 유지합니다. 따라서 여러 클래스가 단일 TransactionTemplate 인스턴스를 공유할 수 있지만, 만약 한 클래스가 다른 설정(예를 들어, 다른 격리 수준)을 가진 TransactionTemplate을 사용해야 한다면, 두 개의 별도 TransactionTemplate 인스턴스를 생성해야 합니다.

공유 주입 가능: 하나의 TransactionTemplate 인스턴스를 여러 서비스에 주입하여 사용할 수 있다는 의미입니다. 즉, 여러 클래스나 서비스가 같은 TransactionTemplate을 재사용할 수 있다는 것입니다.

스레드 안전성과 설정 상태의 유지: TransactionTemplate 인스턴스는 "대화 상태"를 유지하지 않기 때문에 여러 스레드에서 동시에 사용해도 안전합니다. 그러나 이 인스턴스들은 설정 상태(예: 전파 모드, 격리 수준 등)를 유지합니다. 즉, 만약 여러 클래스가 동일한 트랜잭션 설정을 사용한다면 하나의 TransactionTemplate을 공유할 수 있지만, 만약 어떤 클래스가 다른 설정(예를 들어, 다른 격리 수준 등)을 사용해야 한다면, 별도의 TransactionTemplate 인스턴스를 생성해야 한다는 뜻입니다.

정리하면, TransactionTemplate은 여러 서비스에서 공유해서 사용할 수 있지만, 트랜잭션 설정이 달라야 할 경우에는 별도로 인스턴스를 만들어야 한다는 의미입니다.

 

Using the TransactionalOperator

TransactionOperator 사용

 

The TransactionalOperator follows an operator design that is similar to other reactive operators. It uses a callback approach (to free application code from having to do the boilerplate acquisition and release transactional resources) and results in code that is intention driven, in that your code focuses solely on what you want to do.

 

TransactionalOperator는 다른 반응형 연산자와 유사한 연산자 디자인을 따릅니다. 이는 애플리케이션 코드가 트랜잭션 리소스를 획득하고 해제하는 반복 작업을 처리할 필요 없도록 콜백 방식을 사용하며, 결과적으로 코드가 의도 중심이 되어 여러분의 코드가 오로지 수행하고자 하는 작업에만 집중할 수 있게 합니다.

 

As the examples that follow show, using the TransactionalOperator absolutely couples you to Spring’s transaction infrastructure and APIs. Whether or not programmatic transaction management is suitable for your development needs is a decision that you have to make yourself.

다음 예제들이 보여주듯, TransactionalOperator를 사용하면 반드시 Spring의 트랜잭션 인프라와 API에 결합됩니다. 프로그램 방식의 트랜잭션 관리가 개발 요구에 적합한지 여부는 여러분이 스스로 결정해야 하는 문제입니다.

 

Application code that must run in a transactional context and that explicitly uses the TransactionalOperator resembles the next example:

 

다음은 트랜잭션 컨텍스트 내에서 실행되어야 하며 명시적으로 TransactionalOperator를 사용하는 애플리케이션 코드가 다음 예제와 유사함을 나타냅니다:

public class SimpleService implements Service {

	// single TransactionalOperator shared amongst all methods in this instance
	private final TransactionalOperator transactionalOperator;

	// use constructor-injection to supply the ReactiveTransactionManager
	public SimpleService(ReactiveTransactionManager transactionManager) {
		this.transactionalOperator = TransactionalOperator.create(transactionManager);
	}

	public Mono<Object> someServiceMethod() {

		// the code in this method runs in a transactional context

		Mono<Object> update = updateOperation1();

		return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional);
	}
}

 

TransactionalOperator can be used in two ways:

  • Operator-style using Project Reactor types (mono.as(transactionalOperator::transactional))
  • Callback-style for every other case (transactionalOperator.execute(TransactionCallback<T>))

Code within the callback can roll the transaction back by calling the setRollbackOnly() method on the supplied ReactiveTransaction object, as follows:

TransactionalOperator는 두 가지 방식으로 사용할 수 있습니다:

  • Project Reactor 타입을 사용하는 연산자 스타일 (예: mono.as(transactionalOperator::transactional))
  • 그 외 모든 경우를 위한 콜백 스타일 (예: transactionalOperator.execute(TransactionCallback<T>))

콜백 내의 코드는 제공된 ReactiveTransaction 객체에 대해 setRollbackOnly() 메서드를 호출함으로써 트랜잭션을 롤백할 수 있습니다, 다음과 같이:

transactionalOperator.execute(new TransactionCallback<>() {

	public Mono<Object> doInTransaction(ReactiveTransaction status) {
		return updateOperation1().then(updateOperation2)
					.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
		}
	}
});
 


Cancel Signals

In Reactive Streams, a Subscriber can cancel its Subscription and stop its Publisher. Operators in Project Reactor, as well as in other libraries, such as next(), take(long), timeout(Duration), and others can issue cancellations. There is no way to know the reason for the cancellation, whether it is due to an error or a simply lack of interest to consume further. Since version 5.3 cancel signals lead to a roll back. As a result it is important to consider the operators used downstream from a transaction Publisher. In particular in the case of a Flux or other multi-value Publisher, the full output must be consumed to allow the transaction to complete.

Reactive Streams에서는 Subscriber가 자신의 Subscription을 취소하여 Publisher를 중단할 수 있습니다. Project Reactor의 연산자뿐만 아니라 다른 라이브러리의 next(), take(long), timeout(Duration) 등의 연산자들도 취소를 발생시킬 수 있습니다. 취소가 에러로 인한 것인지 아니면 단순히 더 이상 소비에 관심이 없어서 발생한 것인지는 알 방법이 없습니다. 5.3 버전부터 취소 신호는 롤백으로 이어집니다. 그 결과, 트랜잭션 Publisher 하위에서 사용되는 연산자들을 고려하는 것이 중요합니다. 특히 Flux 또는 다른 다중 값 Publisher의 경우, 트랜잭션이 완료되려면 전체 출력이 소비되어야 합니다.

 

Reactive Streams에서는 구독자(Subscriber)가 데이터 스트림(Publisher)을 중단시킬 수 있습니다. 예를 들어, next(), take(long), timeout(Duration) 같은 연산자들이 실행 도중 데이터를 받지 않고 스트림을 취소할 수 있습니다.

취소가 왜 발생했는지(예를 들어, 에러 때문인지 단순히 더 이상 데이터를 소비할 필요가 없어서인지)를 알 수 없습니다.
버전 5.3부터는 이런 취소 신호가 트랜잭션 롤백으로 이어집니다. 즉, 구독자가 데이터를 더 이상 받지 않으면, 진행 중인 트랜잭션이 취소되고 롤백될 수 있습니다.

특히 Flux 같은 여러 값을 내보내는 Publisher의 경우, 트랜잭션이 제대로 완료되려면 모든 데이터가 소비되어야 합니다. 일부만 소비되고 나머지는 취소되면 트랜잭션이 롤백될 수 있습니다. 요약하면, Reactive Streams에서는 데이터 소비를 중간에 취소하면 트랜잭션도 취소될 수 있으므로, 트랜잭션 내에서 모든 데이터를 다 처리해야 트랜잭션이 성공적으로 커밋된다는 뜻입니다.

 

Specifying Transaction Settings

트랜잭션 설정 지정

 

You can specify transaction settings (such as the propagation mode, the isolation level, the timeout, and so forth) for the TransactionalOperator. By default, TransactionalOperator instances have default transactional settings. The following example shows customization of the transactional settings for a specific TransactionalOperator:

TransactionalOperator에 대해 전파 모드, 격리 수준, 타임아웃 등과 같은 트랜잭션 설정을 지정할 수 있습니다. 기본적으로, TransactionalOperator 인스턴스는 기본 트랜잭션 설정을 가지고 있습니다. 다음 예제는 특정 TransactionalOperator에 대한 트랜잭션 설정의 커스터마이징을 보여줍니다.

 

public class SimpleService implements Service {

	private final TransactionalOperator transactionalOperator;

	public SimpleService(ReactiveTransactionManager transactionManager) {
		DefaultTransactionDefinition definition = new DefaultTransactionDefinition();

		// the transaction settings can be set here explicitly if so desired
		definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
		definition.setTimeout(30); // 30 seconds
		// and so forth...

		this.transactionalOperator = TransactionalOperator.create(transactionManager, definition);
	}
}


Using the TransactionManager

TransactionManger 사용

 

The following sections explain programmatic usage of imperative and reactive transaction managers.

다음 섹션들은 명령형 및 반응형 트랜잭션 관리자들의 프로그래밍 방식 사용법을 설명합니다.

 


Using the PlatformTransactionManager

PlatformTransactionManger 사용

 

For imperative transactions, you can use a org.springframework.transaction.PlatformTransactionManager directly to manage your transaction. To do so, pass the implementation of the PlatformTransactionManager you use to your bean through a bean reference. Then, by using the TransactionDefinition and TransactionStatus objects, you can initiate transactions, roll back, and commit. The following example shows how to do so:

명령형 트랜잭션의 경우, org.springframework.transaction.PlatformTransactionManager을(를) 직접 사용하여 트랜잭션을 관리할 수 있습니다. 이를 위해, 여러분이 사용하는 PlatformTransactionManager의 구현체를 빈 참조를 통해 여러분의 빈에 전달합니다. 그런 다음, TransactionDefinition 및 TransactionStatus 객체를 사용하여 트랜잭션을 시작하고, 롤백하며, 커밋할 수 있습니다. 다음 예제는 그 방법을 보여줍니다

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
	// put your business logic here
} catch (MyException ex) {
	txManager.rollback(status);
	throw ex;
}
txManager.commit(status);

 

Using the ReactiveTransactionManager

Reactive TransactionManger 사용

 

When working with reactive transactions, you can use a org.springframework.transaction.ReactiveTransactionManager directly to manage your transaction. To do so, pass the implementation of the ReactiveTransactionManager you use to your bean through a bean reference. Then, by using the TransactionDefinition and ReactiveTransaction objects, you can initiate transactions, roll back, and commit. The following example shows how to do so:

 

반응형 트랜잭션을 다룰 때는, org.springframework.transaction.ReactiveTransactionManager를 직접 사용하여 트랜잭션을 관리할 수 있습니다. 이를 위해, 사용하는 ReactiveTransactionManager의 구현체를 빈 참조를 통해 해당 빈에 전달합니다. 그런 다음, TransactionDefinition 및 ReactiveTransaction 객체를 사용하여 트랜잭션을 시작하고, 롤백하며, 커밋할 수 있습니다. 다음 예제는 그 방법을 보여줍니다.

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

Mono<ReactiveTransaction> reactiveTx = txManager.getReactiveTransaction(def);

reactiveTx.flatMap(status -> {

	Mono<Object> tx = ...; // put your business logic here

	return tx.then(txManager.commit(status))
			.onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex)));
});

 

 

정리

Spring Framework는 프로그램 방식의 트랜잭션 관리를 위해 두 가지 접근 방식을 제공합니다. 하나는 TransactionTemplate 또는 TransactionalOperator와 같이 템플릿과 연산자 기반의 방식을 사용하는 것이고, 다른 하나는 PlatformTransactionManager나 ReactiveTransactionManager와 같은 TransactionManager 구현체를 직접 사용하는 방법입니다.

 

명령형 코드에서는 TransactionTemplate을, 반응형 코드에서는 TransactionalOperator를 사용하는 것이 권장됩니다. 이들 템플릿과 연산자는 콜백 방식을 사용하여 애플리케이션 코드가 트랜잭션 리소스의 획득과 해제 같은 반복적인 작업을 직접 처리하지 않아도 되게 하며, 콜백 내부에서는 필요에 따라 setRollbackOnly() 메서드를 호출하여 트랜잭션 롤백을 지정할 수 있습니다.

 

반면, 직접 TransactionManager를 사용하는 방식은 TransactionDefinition과 TransactionStatus 또는 ReactiveTransaction 객체를 사용해 트랜잭션을 시작하고, 롤백하며, 커밋하는 과정을 프로그래밍 방식으로 구현하는 것입니다.

 

전파 모드, 격리 수준, 타임아웃 등의 트랜잭션 설정은 프로그래밍 방식이나 Spring XML과 같은 설정 파일을 통해 지정할 수 있으며, 템플릿이나 연산자 인스턴스는 기본 설정을 가지고 있다가 필요에 따라 커스터마이징할 수 있습니다.

 

이때 하나의 인스턴스는 스레드 안전하지만 설정 상태를 유지하기 때문에, 서로 다른 트랜잭션 설정을 사용해야 하는 경우에는 별도의 인스턴스를 생성해야 합니다.

 

또한, 반응형 환경에서는 Reactive Streams의 특성상 구독자가 데이터를 중간에 취소하면 트랜잭션이 롤백될 수 있으므로, 다중 값을 처리하는 Flux와 같은 Publisher에서는 전체 데이터가 소비되어야 트랜잭션이 정상적으로 완료된다는 점을 주의해야 합니다.

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

Garbage Collector #3  (0) 2025.03.24
[Spring Docs] Transaction Managment #5  (0) 2025.03.21
[Spring Docs] Transaction Management #3  (0) 2025.03.19
[Spring Docs] Transaction Management #2  (0) 2025.03.18
[Spring Docs] DispatcherServlet #3  (0) 2025.03.17