하나의 서비스에서 여러 데이터베이스를 동시에 처리해야 하는 경우가 있다.
이때 단일 DataSource 기반의 @Transactional만으로는 여러 데이터베이스 간의 트랜잭션 일관성을 보장하기 어렵다.
예를 들어, 하나의 함수에서 A 데이터베이스 수정 → B 데이터베이스 수정 중 오류 발생
이런 상황에서 A와 B 데이터베이스를 모두 함께 롤백하려면 글로벌 트랜잭션(JTA) 이 필요하다.
JTA가 필요없는 예시:
•회원 정보 수정 → A DB만 사용
•인증 정보 수정 → B DB만 사용
이처럼 상황에 따라 특정 데이터베이스만 접근하는 구조라면, 트랜잭션 매니저를 동적으로 선택하는 방식을 참고하는 것이 더 적절할 수 있다.
(관련 내용은 아래 링크 참고)
https://thornapp.tistory.com/10
하나의 DB에서 트랜잭션 매니저를 동적으로 선택하는 방법 (Spring AOP)
배경 및 문제점운영 중인 시스템에서 다음과 같은 상황이 있었다.• 하나의 물리 DB• 특정 역할에 따른 DB 계정 분리• MyBatis + Spring Transaction 사용하지만 Spring의 기본 @Transactional 은→ 트랜잭션
thornapp.tistory.com
아키텍처 구성
• Spring Boot
• Atomikos (JTA Transaction Manager)
• Oracle XA DataSource
• MyBatis
• 다중 DB (Primary / Secondary / Third)
java 코드 예제
@Configuration
public class DataSourceConfig {
/* =====================================================
* XA DataSource 설정
*
* - AtomikosDataSourceBean은 XA 트랜잭션을 지원하는 DataSource
* - 각 DataSource는 서로 다른 DB를 바라보지만
* - JTA TransactionManager에 의해 하나의 글로벌 트랜잭션으로 묶인다
* ===================================================== */
/**
* Primary XA DataSource
* - 기본으로 사용될 DataSource
* - @Primary로 지정하여 주입 시 우선 선택
*/
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return new AtomikosDataSourceBean();
}
/**
* Secondary XA DataSource
*/
@Bean(name = "secondaryDataSource")
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() {
return new AtomikosDataSourceBean();
}
/**
* Third XA DataSource
*/
@Bean(name = "thirdDataSource")
@ConfigurationProperties("spring.datasource.third")
public DataSource thirdDataSource() {
return new AtomikosDataSourceBean();
}
/* =====================================================
* JTA Transaction Manager 설정
*
* - 여러 XA DataSource를 하나의 글로벌 트랜잭션으로 관리
* - @Transactional 하나로 다중 DB 트랜잭션을 제어 가능
* - 내부적으로 2-Phase Commit(2PC) 수행
* ===================================================== */
@Bean
public PlatformTransactionManager transactionManager() throws SystemException {
// UserTransaction: 트랜잭션의 시작 / 커밋 / 롤백을 제어
UserTransactionImp userTx = new UserTransactionImp();
userTx.setTransactionTimeout(30);
// Atomikos의 핵심 트랜잭션 매니저
// 여러 XA Resource(DataSource)를 관리
UserTransactionManager atomikosTxManager = new UserTransactionManager();
atomikosTxManager.init();
// Spring에서 사용하는 JTA 트랜잭션 매니저
// @Transactional 이 이 매니저를 통해 글로벌 트랜잭션을 제어
return new JtaTransactionManager(userTx, atomikosTxManager);
}
/* =====================================================
* SqlSessionFactory 설정
*
* - 각 DataSource마다 별도의 SqlSessionFactory 생성
* - 하지만 트랜잭션은 모두 JTA(TransactionManager) 하나로 관리됨
* ===================================================== */
@Primary
@Bean(name = "primarySqlSessionFactory")
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource dataSource,
ApplicationContext context) throws Exception {
return createSqlSessionFactory(dataSource, context);
}
@Bean(name = "secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory(
@Qualifier("secondaryDataSource") DataSource dataSource,
ApplicationContext context) throws Exception {
return createSqlSessionFactory(dataSource, context);
}
@Bean(name = "thirdSqlSessionFactory")
public SqlSessionFactory thirdSqlSessionFactory(
@Qualifier("thirdDataSource") DataSource dataSource,
ApplicationContext context) throws Exception {
return createSqlSessionFactory(dataSource, context);
}
/**
* SqlSessionFactory 공통 생성 메서드
*
* - DataSource만 다르고 MyBatis 설정은 동일
* - 유지보수를 위해 공통 메서드로 분리
*/
private SqlSessionFactory createSqlSessionFactory(
DataSource dataSource,
ApplicationContext context) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setConfigLocation(
context.getResource("classpath:mybatis/mybatis-config.xml"));
factoryBean.setMapperLocations(
context.getResources("classpath:mapper/**/*.xml"));
return factoryBean.getObject();
}
/* =====================================================
* SqlSessionTemplate 설정
*
* - MyBatis에서 실제 SQL 실행에 사용되는 객체
* - 각각 다른 DB를 바라보지만
* - 트랜잭션은 JTA에 의해 하나로 묶인다
* ===================================================== */
@Primary
@Bean(name = "primarySqlSessionTemplate")
public SqlSessionTemplate primarySqlSessionTemplate(
@Qualifier("primarySqlSessionFactory") SqlSessionFactory factory) {
return new SqlSessionTemplate(factory);
}
@Bean(name = "secondarySqlSessionTemplate")
public SqlSessionTemplate secondarySqlSessionTemplate(
@Qualifier("secondarySqlSessionFactory") SqlSessionFactory factory) {
return new SqlSessionTemplate(factory);
}
@Bean(name = "thirdSqlSessionTemplate")
public SqlSessionTemplate thirdSqlSessionTemplate(
@Qualifier("thirdSqlSessionFactory") SqlSessionFactory factory) {
return new SqlSessionTemplate(factory);
}
}
DB 설정 yml 예제
spring:
datasource:
primary:
unique-resource-name: primary-db
xa-datasource-class-name: oracle.jdbc.xa.client.OracleXADataSource
xa-properties:
URL: jdbc:oracle:thin:@...
user: primary_user
password: primary_password
min-pool-size: 1
max-pool-size: 50
borrow-connection-timeout: 30
maintenance-interval: 60
max-lifetime: 3600
max-idleTime: 60
secondary:
unique-resource-name: secondary-db
xa-datasource-class-name: oracle.jdbc.xa.client.OracleXADataSource
xa-properties:
URL: jdbc:oracle:thin:@...
user: secondary_user
password: secondary_password
min-pool-size: 1
max-pool-size: 50
borrow-connection-timeout: 30
maintenance-interval: 60
max-lifetime: 3600
max-idleTime: 60
third:
unique-resource-name: third-db
xa-datasource-class-name: oracle.jdbc.xa.client.OracleXADataSource
xa-properties:
URL: jdbc:oracle:thin:@...
user: third_user
password: third_password
min-pool-size: 1
max-pool-size: 50
borrow-connection-timeout: 30
maintenance-interval: 60
max-lifetime: 3600
max-idleTime: 60
lifetime 등 설정값은 찾아보시고 본인 프로젝트에 맞게 변경하시기 바랍니다.
적용예시
@Transactional
public void process() {
primaryMapper.insertA();
secondaryMapper.insertB();
thirdMapper.insertC();
// 하나라도 실패하면 전체 롤백
}
주의사항
• unique-resource-name 중복 금지
• 커스텀한 로컬 트랜잭션과 혼용해서 사용 X
• 성능 오버헤드 고려
• XA 지원 DB만 가능
JTA방식 VS AOP기반 트랜잭션 동적 선택방식
| 구분 | Atomikos + JTA | AOP 기반 트랜잭션 선택 |
|---|---|---|
| 트랜잭션 범위 | 여러 DB를 하나의 글로벌 트랜잭션으로 묶음 | DB별 로컬 트랜잭션 |
| 정합성 보장 | ✅ 완전 보장 (2PC) | ❌ 직접 처리 필요 |
| 실패 시 처리 | 자동 전체 롤백 | 보상 트랜잭션 필요 |
| 구현 난이도 | 높음 | 중~낮음 |
| 성능 | 상대적으로 느림 | 빠름 |
| DB 제약 | XA 지원 DB 필요 | 제약 거의 없음 |
| 추천 사용처 | 금융, 정산, 주문, 결제 | 조회 위주, 비핵심 데이터 |
'java > 내용정리' 카테고리의 다른 글
| 하나의 DB에서 트랜잭션 매니저를 동적으로 선택하는 방법 (Spring AOP) (0) | 2025.12.22 |
|---|---|
| 글로벌 시스템 만들 시 주의사항 : 국가별 시간 관리 (Java + Oracle + JavaScript) (1) | 2025.06.14 |
| JWT 정의 및 java 예제코드 정리 (0) | 2025.05.27 |
| Java : 문자열을 더하는 두 가지 방법 – StringBuilder.append() vs String 덧셈(+) 차이점 (0) | 2025.05.23 |
| HMAC 방식 암호화, SHA-256 알고리즘 적용한 예제 알아보기 (2) | 2025.05.23 |

