본문 바로가기

Reading Record/토비의 스프링 3.0

[토비의 스프링 5장] 5.2 서비스 추상화

용어정리

github.com/Java-Bom/ReadingRecord/issues?q=is%3Aissue+is%3Aclosed

 

Java-Bom/ReadingRecord

📚 책 읽고 정리하기 📚. Contribute to Java-Bom/ReadingRecord development by creating an account on GitHub.

github.com

  • 트랜잭션 롤백, 트랜잭션 커밋

트랜잭션 종료 방식에는 롤백과 커밋 두가지가 있다. 트랜잭션이 정상적으로 종료된 후 DB에 반영하는 것을 커밋, 트랜잭션이 비정상적으로 중단되어 원래의 상태로 돌이키는 것을 롤백이라고 한다.

 

  • 트랜잭션 경계 설정

트랜잭션의 시작지점과 끝지점을 설정하는 것. 

 

  • 로컬 트랜잭션

같은 Connection을 공유하는 트랜잭션 

 

  • 글로벌 트랜잭션, JTA

서로 다른 Connection(분산 DB, 또 다른 트랜잭션을 제공하는 API 등)을 사용하는 작업에 대한 트랜잭션. JTA(Java Transaction API)를 사용한다.

 

 

Q. 트랜잭션 매니저(PlatformTransactionManager)를 사용했을 때 커넥션을 공유하는 방법

github.com/Java-Bom/ReadingRecord/issues/198

 

[5.2] 트랜잭션 동기화 동작방식 · Issue #198 · Java-Bom/ReadingRecord

질문 : 트랜잭션 매니저로 추상화 했을때 어떤방식으로 커넥션을 공유하면서 트랜잭션을 유지하는지 정리해보면 좋을듯

github.com

 

수행코드

 

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 이다. 

이렇게 트랜잭션 내에서 커넥션을 공유하게 되는 것이다.