프로그래밍/spring

스프링의 트랜잭션 추상화 PlatformTransactionManager

승민아 2024. 5. 5. 21:45

보통 애플리케이션 구조는 다음과 같이 3가지 계층으로 나눈다.

출처 : 인프런 김영한

서비스 계층에서는 비즈니스 로직을 담당하며 특정 기술에 의존하지 않고 순수 자바 코드로 작성한다.

데이터 접근 계층에서는 JDBC, JPA와 같은 기술로 DB에 접근하는 코드를 작성한다.

 

서비스 계층에서 JDBC 트랜잭션을 사용하기위한 코드는 다음과 같다.

public void somethingWork() {
    Connection connection = dataSource.getConnection();
    try {
        connection.setAutoCommit(false); // 트랜잭션 시작
        bizLogic(); // 비즈니스 로직
        connection.commit();
    } catch (Exception e) {
        connection.rollback();
        throw new IllegalStateException(e);
    } finally {
        connection.setAutoCommit(true);
        connection.close();
    }
}

 

만약 JDBC에서 JPA로 기술 변경하여 트랜잭션을 사용한다면?

public static void main(String[] args) {
    //엔티티 매니저 팩토리 생성
    EntityManagerFactory emf =
        Persistence.createEntityManagerFactory("jpabook");
    EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성
    EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
    try {
         tx.begin(); //트랜잭션 시작
         logic(em); //비즈니스 로직
         tx.commit();//트랜잭션 커밋
    } catch (Exception e) {
        tx.rollback(); //트랜잭션 롤백
    } finally {
        em.close(); //엔티티 매니저 종료
    }
    emf.close(); //엔티티 매니저 팩토리 종료
}

 

서비스 계층에서 트랜잭션을 사용하는 코드 또한 변경이 된다.

서비스 계층의 코드를 변경하지 않기 위해 트랜잭션 기능을 추상화하면 된다.

 

예를 들어 다음과 같은 인터페이스를 만들어서

public interface TxManager {
    begin();
    commit();
    rollback();
}

JDBC, JPA 트랜잭션에 맞는 구현체를 DI하면 된다.

 

스프링에서는 PlatformTransactionManager 인터페이스를 통해 추상화를 해두었다.

출처 : 인프런 김영한

 

PlatformTransactionManager 인터페이스

public interface PlatformTransactionManager extends TransactionManager {
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
	void commit(TransactionStatus status) throws TransactionException;
	void rollback(TransactionStatus status) throws TransactionException;
}

인터페이스와 그 구현체를 트랜잭션 매니저라고 부르겠다.

 

트랜잭션 매니저 역할

  • 트랜잭션 추상화 : 다른 트랜잭션 기술을 사용한다면 구현체만 바꾸면 된다.
  • 리소스 동기화 : 트랜잭션 유지를 위해 트랜잭션 시작부터 끝까지 같은 커넥션을 유지한다.

출처 : 인프런 김영한

스프링은 트랜잭션 동기화 매니저를 제공하며

트랜잭션 매니저는 내부에서 트랜잭션 동기화 매니저를 사용한다.

따라서 커넥션이 필요하면 트랜잭션 동기화 매니저에서 획득한다.

 

트랜잭션 매니저를 사용하는 코드는 다음과 같다.

/* MemberServiceTest */
// 커넥션을 얻는 방법으로 JDBC DriverManager(신규 커넥션 생성)를 직접 사용.
// 스프링은 DriverManager도 DataSource를 통해서 사용할 수 있도록 DriverManagerDataSource라는 DataSource 를 구현한 클래스를 제공한다.
// DriverManagerDataSource 를 통해서 DriverManager를 사용
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);

// JDBC용 트랜잭션 매니저 - DataSourceTransactionManager
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);

memberRepository = new MemberRepository(dataSource);
memberSerive = new MemberService(transactionManager, memberRepository);


/* MemberRepository - dataSource를 주입 받아 사용 */
Connection connection = DataSourceUtils.getConnection(dataSource); // 트랜잭션 동기화를 위해 DataSourceUtils를 통해 획득

JdbcUtils.closeResultSet(ResultSet rs);
JdbcUtils.closeStatement(Statement stmt);
DataSourceUtils.releaseConnection(connection, dataSource);


/* MemberServicve - transactionManager, memberRepository를 주입 받아 사용 */
private PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
private MemberRepository memberRepository = new MemberRepository(dataSource);

// 트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition();
try {
    bizLogic();
    transactionManager.commit(status); //성공시 커밋
} catch (Exception e) {
    transactionManager.rollback(status); //실패시 롤백
}
  • PlatformTransactionManager : 현재 JDBC 기술을 사용하기에 DataSourceTransactionManger를 주입 받음.
  • DataSourceUtils.getConnection() : 트랜잭션 동기화 매니저가 관리하는 커넥션이 있다면 그것을 반환. 없다면 새로 커넥션 생성
    • TransactionStatus : 트랜잭션 상태 정보가 포함됨. 커밋 롤백을 위해 필요.
  • DataSourceUtils.releaseConnection() : 트랜잭션을 위해 동기화된 커넥션은 닫지 않고 유지 아니면 닫아버림.
  • new DefaultTransactionDefinition() : 트랜잭션 관련 옵션 지정

 

JDBC에서 JPA로 변경을 윈한다면

PlatformTransactionManger에 DataSourceTransactionManger가 아닌 JpaTransactionManger를 주입하면 된다.

이제 서비스 코드의 트랜잭션은 JDBC 기술에 의존하지 않는다. PlatformTransactionManger만 주입 변경해주면 된다.

 

마지막으로 트랜잭션 매니저의 전체 동작을 보자.

트랜잭션 시작

  1. 서비스 계층에서 트랜잭션 시작
  2. 트랜잭션 시작을 위해 데이터 소스에서 커넥션 획득
  3. 수동 커밋 모드로 변경해서 DB 트랜잭션 시작
  4. 커넥션을 트랜잭션 동기화 매니저에 보관

 

로직 실행

 6. 서비스 계층에서 리포지토리 메서드들을 호출

 7. 리포지토리 메서드들은 트랜잭션이 시작된 커넥션을 획득 ( 트랜잭션 동기화 매니저에 보관된 동기화된 커넥션 획득 )

 8. 동기화된 커넥션으로 SQL 수행

 

트랜잭션 종료

 9. 트랜잭션 종료 - 트랜잭션 롤백 또는 커밋

 10. 트랜잭션 동기화 매니저를 통해 동기화된 커넥션 획득

 11. 획득 커넥션으로 트랜잭션을 커밋 또는 롤백

 12. con.setAutoCommit(true), con.close()와 같은 리소스 정리