용어정리
github.com/Java-Bom/ReadingRecord/issues?q=is%3Aissue+is%3Aclosed
- 트랜잭션 롤백, 트랜잭션 커밋
트랜잭션 종료 방식에는 롤백과 커밋 두가지가 있다. 트랜잭션이 정상적으로 종료된 후 DB에 반영하는 것을 커밋, 트랜잭션이 비정상적으로 중단되어 원래의 상태로 돌이키는 것을 롤백이라고 한다.
- 트랜잭션 경계 설정
트랜잭션의 시작지점과 끝지점을 설정하는 것.
- 로컬 트랜잭션
같은 Connection을 공유하는 트랜잭션
- 글로벌 트랜잭션, JTA
서로 다른 Connection(분산 DB, 또 다른 트랜잭션을 제공하는 API 등)을 사용하는 작업에 대한 트랜잭션. JTA(Java Transaction API)를 사용한다.
Q. 트랜잭션 매니저(PlatformTransactionManager)를 사용했을 때 커넥션을 공유하는 방법
github.com/Java-Bom/ReadingRecord/issues/198
수행코드
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
users.forEach(this::upgrade);
this.transactionManager.commit(status); // 트랜잭션 커밋
} catch (RuntimeException ex) {
this.transactionManager.rollback(status); // 트랜잭션 롤백
}
PlatformTransactionManager
PlatformTransactionManager.getTransaction 내부를 먼저 살펴보면
AbstractPlatformTransactionManager 에서 getTransaction을 구현 해놓은 것을 알 수 있다.(JtaTransactionManager, DataSourceTransaction 에서도 해당 추상클래스를 사용하는걸 보면 거의 표준인듯하다
AbstractPlatformTransactionManager#getTransaction
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
... 생략 ...
try {
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
... 생략 ...
}
트랜잭션을 처음 시작할 때 어떻게 Connection을 동기화저장소에 저장하는지 알기 위해 startTransaction 을 따라가보면 doBegin 메서드를 호출하고 doBegin 메서드는 추상메서드이며 각각의 트랜잭션 제공 클래스에서 구현을 해야한다.
JDBC 로컬 트랜잭션을 기준으로 살펴보기 위해 DataSourceTransactionManager에서 구현해놓은 doBegin을 따라갔다.
DataSourceTransactionManager#doBegin
Connection 을 새로 만드는 익숙한 코드와 con.setAutoCommit(false)를 볼 수 있다. getTransaction 메서드로 커넥션을 얻어오며 트랜잭션 시작까지 해준다는 설명을 여기에서 이해할 수 있었다.
그리고, TransactionSynchronizationManager.bindResource(..) 를 타고가면,
resources.set(map)
resources에 커넥션정보를 key, value 형태로 저장하는 것을 볼 수 있다.
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
이 resources는 ThreadLocal에 저장되는데, 같은 Thread끼리 커넥션을 공유할 수 있게 되는 것이다.
여기까지 PlatformTransactionManager를 통해 ThreadLocal에 처음 생성된 커넥션이 저장되는 것을 보았다면 이제 어떻게 여기에서 꺼내쓰는지를 확인해보자.
JdbcTemplate#update 메서드부터 쭉쭉 타고들어가서 아래 protected 메서드의 execute 를 확인해보았다.
protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)
throws DataAccessException {
logger.debug("Executing prepared SQL update");
return updateCount(execute(psc, ps -> { // 이 execute 메서드!
try {
if (pss != null) {
pss.setValues(ps);
}
int rows = ps.executeUpdate();
if (logger.isTraceEnabled()) {
logger.trace("SQL update affected " + rows + " rows");
}
return rows;
}
finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}));
}
내부를 살펴보면 DataSourceUtils.getConnection 을 통해 Connection을 얻어오는 것을 볼 수 있다.
Connection con = DataSourceUtils.getConnection(obtainDataSource());
책에서도 나와있다시피 DataSourceUtils을 사용하면 알아서 동기화 저장소에 Connection이 있다면 해당 커넥션을 얻어오고 아니라면 생성해주어야한다. 내부를 살펴보자.
DataSourceUtils#doGetConnection
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
TransactionSynchronizationManager#doGetResource
Map<Object, Object> map = resources.get();
커넥션을 얻어오는 메서드를 타고타고 들어가면 결국 resources에서 커넥션을 얻어오게되는데, 여기서 resources가 처음에 저장했던 그 ThreadLocal 이다.
이렇게 트랜잭션 내에서 커넥션을 공유하게 되는 것이다.
'Reading Record > 토비의 스프링 3.0' 카테고리의 다른 글
[토비의 스프링 3장] 템플릿 3.5 ~ 3.7 (0) | 2020.12.13 |
---|---|
1.8 ~ 2.2 (0) | 2020.11.27 |
[토비의 스프링] [1장] 오브젝트와 의존관계 1.4 ~ 1.7 (0) | 2020.11.05 |