[Spring] - 트랜잭션 동기화 (TransactionSynchronizationManager)

리소스 동기화 - TransactionSynchronizationManager

트랜잭션을 유지하기 위해서는 트랜잭션 시작부터 끝까지 같은 데이터 베이스 Connection 을 유지해야 합니다. 스프링에서는 트랜잭션 동기화를 위해 트랜잭션 동기화 매니저를 제공합니다.

  1. 트랜잭션 매니저는 DataSource 를 통해 데이터 베이스 Connection 을 생성하고 트랜잭션을 시작합니다.
  2. 트랜잭션 매니저는 트랜잭션이 시작된 Connection 을 트랜잭션 동기화 매니저에 저장합니다.
  3. Repository 는 트랜잭션 동기화 매니저에 저장된 Connection 을 사용합니다.
  4. 트랜잭션이 종료되면 트랜잭션 매니저는 트랜잭션 동기화 매니저에 저장된 Connection 을 통해 트랜잭션을 종료하고 Connection 을 닫습니다.

TransactionSynchronizationManager

TransactionSynchronizationManager 는 ThreadLocal 을 사용해서 각 쓰레드별 데이터 베이스 Connection 을 저장하고 사용할 수 있도록 있습니다.

TransactionSynchronizationManager 사용하기

TransactionSynchronizationManager 에 저장된 Connection 을 가져오기 위해 DataSourceUtils 를 사용합니다.

  • Connection getConnection(DataSource dataSource)
    • DataSource 를 이용해 Database Connection 을 가져옵니다.
    • Connection 이 없을 경우 생성하고 이미 있을 경우 ThreadLocal 에 저장된 Connection 을 가져옵니다.
  • void releaseConnection(@Nullable Connection con, @Nullable DataSource dataSource)
    • DataSource 에 연결된 Database Connection 을 닫습니다.
    • 만약 해당 Connection 을 사용중인 프로세스가 있을 경우 닫지 않고 반환합니다.
private Connection getConnection() throws SQLException {
//주의! 트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용해야 한다.
Connection con = DataSourceUtils.getConnection(dataSource);
log.info("get connection={}, class={}", con, con.getClass());
return con;
}
private void close(Connection con, Statement stmt, ResultSet rs) {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
//주의! 트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용해야 한다.
DataSourceUtils.releaseConnection(con, dataSource);
}

트랜잭션 매니저 사용하기

트랜잭션을 사용하기 위한 방법으로 PlatformTransactionManager 를 사용합니다.

  • TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
    • 트랜잭션 매니저를 이용해 트랜잭션을 시작한 후 트랜잭션 정보가 들어 있는 TransactionStatus 객체를 반환합니다.
  • void commit(TransactionStatus status)
    • Commit 을 수행합니다.
  • void rollback(TransactionStatus status)
    • Rollback 을 수행합니다.
  1. 서비스 계층에서 transactionManager.getTransaction 메소드를 통해 트랜잭션을 실행합니다.
    1. 트랜잭션을 시작하기 위해서는 데이터베이스 커넥션이 필요합니다. 트랜잭션 매니저는 데이터소스를 이용해 커넥션을 생성합니다.
  2. 커넥션을 수동 커밋 모드로 변경해 실제 데이터 베이스 트랜잭션을 실행합니다.
  3. 커넥션을 트랜잭션 동기화 매니저에 저장합니다.
  4. 트랜잭션 동기화 매니저는 ThreadLocal 에 Connection 을 저장합니다.
@Slf4j
@RequiredArgsConstructor
public class MemberServiceV3_1 {

private final PlatformTransactionManager transactionManager;
private final MemberRepositoryV3 memberRepository;

public void accountTransfer(String fromId, String toId, int money) throws SQLException {
//트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

try {
//비즈니스 로직
bizLogic(fromId, toId, money);
transactionManager.commit(status); //성공시 커밋
} catch (Exception e) {
transactionManager.rollback(status); //실패시 롤백
throw new IllegalStateException(e);
}

}

private void bizLogic(String fromId, String toId, int money) throws SQLException {
Member fromMember = memberRepository.findById(fromId);
Member toMember = memberRepository.findById(toId);

memberRepository.update(fromId, fromMember.getMoney() - money);
validation(toMember);
memberRepository.update(toId, toMember.getMoney() + money);
}

private void validation(Member toMember) {
if (toMember.getMemberId().equals("ex")) {
throw new IllegalStateException("이체중 예외 발생");
}
}

}
  1. 서비스에서 비즈니스 로직을 실행하면서 레포지토리 메소드들을 호출합니다.
  2. 레포지토리 메소드들은 DataSource.getConnection 메소드를 이용해 트랜잭션 동기화 매니저에 저장된 커넥션을 가져와 사용합니다.

  1. Commit 이나 Rollback 을 하게 되면 트랜잭션은 종료 됩니다.
  2. 트랜잭션을 종료하기 위해 트랜잭션 동기화 매니저에 저장된 커넥션 정보를 가져옵니다.
  3. 가져온 커넥션을 이용해 데이터베이스에 Commit 이나 Rollback 을 진행합니다.
  4. 전체 리소스를 정리합니다.
    1. 트랜잭션 동기화 매니저에 저장된 Connection 을 제거합니다.
    2. 커넥션 정보를 AutoCommit 모드로 다시 변경합니다.
    3. 커넥션 close 를 통해 커넥션을 종료합니다.

Share