반응형

환경: springboot3.3

 

기존에 멀티 데이터베이스를 쓸 때 분산 트랜젝션을 위해 아래와 같이 ChainedTransaction을 사용하였는데..

@Configuration
public class ChainedTransactionConfiguration {

  @Primary
  @Bean(Constants.CHAINED_TRANSACTION_MANAGER)
  public PlatformTransactionManager transactionManager(@Qualifier(Constants.USER_TRANSACTION_MANAGER) PlatformTransactionManager userPlatformTransactionManager,
      @Qualifier(Constants.LOG_TRANSACTION_MANAGER) PlatformTransactionManager logPlatformTransactionManager,
      @Qualifier(Constants.STATIC_TRANSACTION_MANAGER) PlatformTransactionManager staticPlatformTransactionManager) {
    return new ChainedTransactionManager(userPlatformTransactionManager, logPlatformTransactionManager, staticPlatformTransactionManager);
  }
}

아래와 같이 Deprecated 되었다.

여러 대안을 찾다가 JtaTransaction이 있어 사용가능한지 확인해 본다.

 

조건은

1. 멀티 데이터베이스이기 때문에 각각에 대해 단일 Transactional을 설정할 수 있어야 하고

2. 필요에 따라 복합 트랜젝션도 가능해야 한다.

 

우선 JtaTransaction이 뭔지 간단히 알아보자.

JtaTransactionManager는 기본적으로 여러 데이터베이스에 걸쳐 트랜잭션을 처리하는 역할을 한다. 그러나 이를 제대로 활성화하려면 다음과 같은 조건을 충족해야 한다:

  • XA 데이터 소스 설정: 분산 트랜잭션을 사용하려면 XADataSource를 사용해야 한다. 예를 들어, MySQL을 사용할 경우 MysqlXADataSource를 사용해야 하며, 다른 데이터베이스도 XA 지원을 해야 한다.
  • 트랜잭션 관리자의 설정: JtaTransactionManager는 기본적으로 JTA를 사용하여 트랜잭션을 관리하지만, 분산 트랜잭션을 활성화하려면 여러 데이터 소스를 연결하고 이를 관리할 수 있는 TransactionManager 설정이 필요하다.
@Configuration
public class JtaDbConfig {

    @Bean(name = "chainedTransaction")
    public JtaTransactionManager transactionManager() {
        JtaTransactionManager transactionManager = new JtaTransactionManager();
        // JTA 트랜잭션 매니저 설정
        return transactionManager;
    }

    // DataSource 1 설정 (XA DataSource)
    @Bean
    public DataSource dataSource1() {
        MysqlXADataSource dataSource = new MysqlXADataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/db1");
        dataSource.setUser("user1");
        dataSource.setPassword("pass1");
        return dataSource;
    }

    // DataSource 2 설정 (XA DataSource)
    @Bean
    public DataSource dataSource2() {
        MysqlXADataSource dataSource = new MysqlXADataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/db2");
        dataSource.setUser("user2");
        dataSource.setPassword("pass2");
        return dataSource;
    }

    // EntityManagerFactory 설정 (각각의 데이터베이스용)
    @Bean(name = "entityManagerFactory1")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory1(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(dataSource1())
                .packages("com.example.entity1")
                .build();
    }

    @Bean(name = "entityManagerFactory2")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory2(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(dataSource2())
                .packages("com.example.entity2")
                .build();
    }
}

JtaTransactionManager 도 단일 트랜젝션 관리 가능한가?

  1. JTA 트랜잭션 관리의 범위:
    • JtaTransactionManager는 분산 트랜잭션(XA 트랜잭션)을 관리하는 데 최적화되어 있지만, 단일 데이터 소스에서의 트랜잭션도 처리할 수 있음
    • 단일 데이터 소스만 사용할 경우에도 JTA 프로토콜을 통해 트랜잭션이 시작되고 종료됨
  2. 단일 트랜잭션 시 처리 동작:
    • 단일 데이터 소스에서 JtaTransactionManager는 해당 데이터 소스에서의 트랜잭션을 관리함
    • 단일 데이터 소스 환경에서는 JpaTransactionManager나 DataSourceTransactionManager처럼 작동함
    • 단일 트랜잭션 환경에서는 JpaTransactionManager나 DataSourceTransactionManager가 더 효율적일 수 있다. 이는 JTA 오버헤드가 없기 때문
    • 분산 트랜잭션이 필요 없는 경우 굳이 JtaTransactionManager를 사용할 필요는 없음

 

참고:

  1. JpaTransactionManager는 JPA에 특화되어 있으며, 트랜잭션이 하나의 데이터베이스일 경우에 적합
  2. JtaTransactionManager는 JTA를 지원하며, XA 데이터 소스를 사용하는 분산 트랜잭션을 관리할 수 있음

JtaTransactionManager는 단일 트랜잭션도 처리할 수 있지만, 분산 트랜잭션이 필요 없는 경우에는 더 가벼운 트랜잭션 매니저(JpaTransactionManager 또는 DataSourceTransactionManager)를 사용하는 것이 더 효율적임. 하지만 프로젝트 환경에서 단일 및 분산 트랜잭션이 모두 필요하다면 JtaTransactionManager를 사용해 통합적으로 관리 가능

그럼 단일 트랜젝션이 필요할 경우 더 가볍게 설정할 수는 없을까?

그거슨 불가..

JtaTransactionManager가 기본적으로 JTA 규격에 따라 동작하며, 트랜잭션의 범위는 리소스에 따라 자동으로 결정되기 때문이다..

정 필요하면 아래처럼 JpaTransactionManager / JtaTransactionManager 각각 만들어서 필요에 따라 transactionManager를 지정하는 방법뿐.. 이라는데 이건 좀 아닌 듯....

@Configuration
public class DataSourceConfig {

    // 첫 번째 데이터베이스 - 단일 트랜잭션용
    @Bean(name = "dataSource1Hikari")
    public DataSource dataSource1Hikari() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/db1");
        dataSource.setUsername("user1");
        dataSource.setPassword("password1");
        return dataSource;
    }

    // 첫 번째 데이터베이스 - 분산 트랜잭션용
    @Bean(name = "dataSource1XA")
    public DataSource dataSource1XA() {
        MysqlXADataSource xaDataSource = new MysqlXADataSource();
        xaDataSource.setUrl("jdbc:mysql://localhost:3306/db1");
        xaDataSource.setUser("user1");
        xaDataSource.setPassword("password1");
        return xaDataSource;
    }

    // 두 번째 데이터베이스 - 단일 트랜잭션용
    @Bean(name = "dataSource2Hikari")
    public DataSource dataSource2Hikari() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/db2");
        dataSource.setUsername("user2");
        dataSource.setPassword("password2");
        return dataSource;
    }

    // 두 번째 데이터베이스 - 분산 트랜잭션용
    @Bean(name = "dataSource2XA")
    public DataSource dataSource2XA() {
        MysqlXADataSource xaDataSource = new MysqlXADataSource();
        xaDataSource.setUrl("jdbc:mysql://localhost:3306/db2");
        xaDataSource.setUser("user2");
        xaDataSource.setPassword("password2");
        return xaDataSource;
    }
}
@Configuration
public class TransactionManagerConfig {

    // 첫 번째 데이터베이스 - 단일 트랜잭션
    @Bean(name = "transactionManager1")
    public DataSourceTransactionManager transactionManager1(
            @Qualifier("dataSource1Hikari") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    // 두 번째 데이터베이스 - 단일 트랜잭션
    @Bean(name = "transactionManager2")
    public DataSourceTransactionManager transactionManager2(
            @Qualifier("dataSource2Hikari") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    // JTA 트랜잭션 매니저 (분산 트랜잭션 관리)
    @Bean(name = "jtaTransactionManager")
    public JtaTransactionManager jtaTransactionManager(
            @Qualifier("dataSource1XA") DataSource dataSource1XA,
            @Qualifier("dataSource2XA") DataSource dataSource2XA) {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        return new JtaTransactionManager(userTransactionImp, userTransactionManager);
    }
}
@Service
public class DbService {

    @Transactional(transactionManager = "transactionManager1")
    public void performDb1Operation() {
        // 첫 번째 데이터베이스 트랜잭션 작업
    }

    @Transactional(transactionManager = "transactionManager2")
    public void performDb2Operation() {
        // 두 번째 데이터베이스 트랜잭션 작업
    }

    @Transactional(transactionManager = "jtaTransactionManager")
    public void performMultiDbOperation() {
        // DB1과 DB2를 조율하는 분산 트랜잭션 작업
    }
}

JTA 프로토콜: 2단계 커밋 (2PC)

JTA는 2PC (Two-Phase Commit) 프로토콜을 사용하여 분산 트랜잭션의 원자성과 일관성을 보장한다. 이 프로토콜은 다음 두 단계를 포함한다.

  1. 1단계: Prepare
    • 트랜잭션 관리자(Transaction Manager)는 모든 자원 관리자(XAResource)에 "Prepare" 메시지를 보냄
    • 각 자원 관리자는 트랜잭션을 준비하고, 성공 여부를 반환(예: VoteCommit 또는 VoteRollback)
  2. 2단계: Commit or Rollback
    • 모든 자원이 VoteCommit을 반환하면 트랜잭션 관리자는 "Commit" 메시지를 보내 트랜잭션을 커밋
    • 하나라도 VoteRollback을 반환하면 모든 자원에 "Rollback" 메시지를 보내 트랜잭션을 롤백
728x90
반응형

+ Recent posts