1. 글을 작성하게 된 계기
사람들과 스프링 서버를 만들어보는 프로젝트를 진행하며 Transactional의 동작원리에 대해 질문받았습니다. 이를 잘 이해하기 위해서는 TransactionManager에 대해 한 번 정리해야겠다는 생각이 들어 글을 작성하게 되었습니다.
2. TransactionManager
TransactionManager는 트랜잭션 관리를 추상화하는 인터페이스입니다.
Marker interface for Spring transaction manager implementations, either traditional or reactive.
이는 마커 인터페이스로, 그 자체로서는 아무런 역할을 하지 않습니다.
1
2
public interface TransactionManager {
}
마커 인터페이스는 디자인 패턴 중 하나로, 빈 인터페이스를 말합니다. 자체로서는 아무 역할도 하지 않지만, 이를 사용해 상속 계층도를 만들고, 해당 객체에 특정한 성질이 있다는 것을 알릴 수 있습니다. 스프링에서는 마커 인터페이스를 나타내기 위해 어노테이션을 사용하기도 하는데, 이를 통해 바이트 코드에서도 메타데이터를 제공할 수 있기 때문입니다.
TransactionManager의 상속 계층도는 다음과 같습니다. 이에 대해 조금 더 상세하게 살펴보겠습니다.
해당 글은 JPA는 배제하고 Jdbc를 기준으로 설명하겠습니다.
2-1. PlatformTransactionManager
PlatformTransactionManager는 트랜잭션 관리를 위한 공통 인터페이스 입니다.
이를 통해 JDBC, JPA, JTA 등과 같은 특정 기술이나 구현체에 종속되지 않고 기술/구현체를 자유롭게 변경 할 수 있습니다.
1
2
3
4
5
6
7
8
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
정의된 메서드를 보면, 모든 트랜잭션에서 공통으로 사용되는 메서드만 정의된 것을 볼 수 있습니다.
구현체들을 간략히 살펴보면, Jdbc 기반 라이브러리를 사용하는 경우, 구현체로 DataSourceTransactionManager를, Hibernate를 사용하는 경우 HibernateTransactionManager를, JPA는 JpaTransactionManager를 사용합니다.
Jdbc: DataSourceTransactionManagerHibernate: HibernateTransactionManagerJPA: JpaTransactionManager
스프링에서는 이는 직접 사용하기보다는 TransactionTemplate 또는 AOP를 통해 사용할 것을 권장하고 있습니다.
다음과 같이요. 스프링에는 프로그래밍적 트랜잭션(Programmatic transaction) 과 선언적 트랜잭션(Declarative transaction) 이 존재하는데, 이에 대해서는 별도로 학습해 보실 것을 권장해 드립니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class ExampleService {
private final PlatformTransactionManager transactionTemplate;
public ExampleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public Object invoke() {
return transactionTemplate.execute(status -> {
// 비즈니스 처리
return result;
});
}
}
1
2
3
4
5
6
7
8
9
@Service
public class ExampleService {
@Transactional
public void invoke() {
// 비즈니스 처리
}
}
2-2. AbstractPlatformTransactionManager
AbstractPlatformTransactionManager는 스프링에서 명령형 트랜잭션 인프라의 핵심적 인터페이스입니다.
Abstract base class that implements Spring’s standard transaction workflow, serving as basis for concrete platform transaction managers like JtaTransactionManager.
이는 PlatformTransactionManager가 제공하는 기능 외에도, 트랜잭션 관리를 위한 공통 로직과 트랜잭션 동기화, 전파 정책을 처리하는 추가/구체적인 기능 을 제공합니다.
- determines if there is an existing transaction.
- applies the appropriate propagation behavior.
- suspends and resumes transactions if necessary.
- checks the rollback-only flag on commit.
- applies the appropriate modification on rollback (actual rollback or setting rollback-only).
- triggers registered synchronization callbacks (if transaction synchronization is active).
이를 통해 트랜잭션 매니저를 보다 쉽게 구현/확장 할 수 있도록 합니다. 즉, PlatformTransactionManager가 제공하는 메서드 뿐 아니라, 트랜잭션 전파, 동기화, 타임아웃 과 같은 추가적인 기능을 제공하며, AbstractPlatformTransactionManager의 하위 구현체는 이런 기능을 공통으로 사용할 수 있어, 트랜잭션 관리 기능의 확장성과 재사용성을 높일 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
......
// PlatformTransactionManager를 구현 상속해 조금 더 구체적인 구현을 제공
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
......
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
......
}
else
{
......
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
// 구체적인 추가 구현 메서드 제공
protected void prepareSynchronization(
DefaultTransactionStatus status,
TransactionDefinition definition
) {
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
definition.getIsolationLevel() : null);
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
TransactionSynchronizationManager.initSynchronization();
}
}
......
protected int determineTimeout(TransactionDefinition definition) {
if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
return definition.getTimeout();
}
return getDefaultTimeout();
}
......
}
2-3. ResourceTransactionManager
ResourceTransactionManager는 PlatformTransactionManager 인터페이스의 확장으로, 단일 대상 리소스에서 동작하는 네이티브 리소스 트랜잭션 관리자 입니다.
1
2
3
public interface ResourceTransactionManager extends PlatformTransactionManager {
Object getResourceFactory();
}
단일 대상 리소스란, 하나의 데이터베이스, 또는 하나의 메시지 큐등 단일 리소스 대상 의 트랜잭션을 말합니다.
1
2
3
4
5
6
7
8
9
10
11
12
@Service
@RequiredArgsConstructor
class MemberService {
private final MySQL mySQL;
@Transactional
public void signUp(Member member) {
mySQL.save(member);
}
}
반대로 JTA(Java Transaction API) 는 여러 다른 타입의 리소스에 걸친 트랜잭션을 관리합니다. 다양한 리소스에 걸친 트랜잭션이란, 한 트랜잭션 내에서 데이터베이스, 메시지 큐 등 다양한 리소스에 대한 트랜잭션을 하나의 트랜잭션으로 관리하는 분산 트랜잭션을 말합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
@RequiredArgsConstructor
class MemberService {
private final MySQL mySQL;
private final MongoDb mongoDb;
private final MessageQueue messageQueue;
@Transactional
public void signUp(Member member) {
Member newMember = mySQL.save(member);
mongoDb.save(new MemberDocument(newMember));
messageQueue.sendMessage(new Message(newMember));
}
}
이는 다음과 같은 구현체를 가집니다.
2-4. DataSourceTransactionManager
DataSourceTransactionManager는 Jdbc DataSource를 사용하는 데이터 액세스의 트랜잭션을 관리합니다. 이는 Jdbc 커넥션을 가지고 DataSource를 통해 작업합니다.
내부 구현을 보더라도 DataSource를 가지고 이를 통해 작업하는 것을 볼 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@SuppressWarnings("serial")
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean {
@SuppressWarnings("serial")
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
@Nullable
private DataSource dataSource;
private final boolean enforceReadOnly = false;
......
public DataSourceTransactionManager(DataSource dataSource) {
this();
setDataSource(dataSource);
afterPropertiesSet();
}
......
@Override
public void afterPropertiesSet() {
if (getDataSource() == null) {
throw new IllegalArgumentException("Property 'dataSource' is required");
}
}
@Override
public Object getResourceFactory() {
return obtainDataSource();
}
@Override
protected Object doGetTransaction() {
......
return txObject;
}
@Override
protected boolean isExistingTransaction(Object transaction) {
......
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
......
}
......
@Override
protected void doCommit(DefaultTransactionStatus status) {
......
}
@Override
protected void doRollback(DefaultTransactionStatus status) {
......
}
}
}
추가로 스프링 5.3부터 JdbcTransactionManager가 등장했는데, 이는 DataSourceTransactionManager를 상속받아 추가적인 기능을 제공합니다. 이에 대한 설명은 해당 댓글을 참조해 주세요.
The only difference is exception translation: Like with JpaTransactionManager, there is DataAccessException translation on commit here. Where DataSourceTransactionManager will only ever throw TransactionSystemException on commit, JdbcTransactionManager is more sophisticated and translates locking failures etc to corresponding DataAccessException subclasses. Note that calling code needs to be prepared for that, not exclusively expecting TransactionSystemException in such scenarios. That’s the main reason why those are two separate transaction manager classes to choose from.
2-5. TransactionSynchronizationManager
TransactionSynchronizationManager는 현재 쓰레드에 대한 트랜잭션 동기화, 트랜잭션 상태 정보 관리 및 리소스 바인딩, 트랜잭션과 관련된 콜백 실행 등을 담당합니다.
이는 쓰레드 로컬(ThreadLocal)을 사용해 동기화 작업을 하며, 이를 통해 동시성 문제로부터 안전할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
......
}
트랜잭션에 관한 여러 정보는 여러 쓰레드 로컬에 나누어 저장됩니다. 각 쓰레드 로컬의 역할은 다음과 같습니다.
resources: 현재 쓰레드와 관련된 트랜잭션 리소스를 저장합니다.synchronizations: 트랜잭션은 beforeCommit, readOnly, afterCommit 등의 상태를 가지는데, 이에 대한 정보를 저장합니다.currentTransactionName: 각 트랜잭션은 이름을 가지며, 이름을 저장합니다.currentTransactionReadOnly: 각 트랜잭션의 읽기 속성에 대한 정보를 저장합니다.currentTransactionIsolationLevel: 각 트랜잭션의 격리수준에 대한 정보를 저장합니다.actualTransactionActive: 커밋/롤백 여부에 따른 트랜잭션 활성화에 대한 정보를 저장합니다.
3. 정리
스프링의 TransactionManager에 대해 살펴보았습니다. 스프링은 TransactionManager를 통해 상속 계층도를 만들어 트랜잭션을 추상화하고, 일관되게 적용할 수 있게 해줍니다. 따라서 어떤 트랜잭션 매니저가 존재하는지, 이를 통해 어떻게 트랜잭션을 일관되게 관리하는지에 대해 정리하고, 이를 통해 트랜잭션을 조금 더 잘 사용할 수 있도록 학습해 볼 것을 권장해 드립니다.