일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 활성프로브
- 영속화
- JPA
- OneToOne
- K8s
- ManyToMany
- 종료코드
- 디자인 패턴
- SpringBoot
- docker
- Entity
- SpringBoot 2.0
- mybatis
- 다중 트랜잭션
- Multi Datasource
- Design Pattern
- Multi Transaction
- openjdk
- 다중 데이타소스
- OneToMany
- chroot exit code
- 트랜잭션 쓰기 지연
- exit code
- 변경 감지
- MaxRAMPercentage
- ManyToOne
- Java
- JDK
- OracleJDK
- dirty check
- Today
- Total
조금 평범한 개발 이야기
JPA 다중 데이타소스 관리 (2) 본문
트랜잭션
데이타를 사용하고 관리할때 가장 중요하게 고려되어야 하는 것은 데이타의 무결성을 어떻게 보장 할 것인가 입니다.
이를 위해 데이타베이스에서는 트랜잭션 이라는 작업 단위를 제공 하는데 트랜잭션은 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 영속성(Durability) 등의 속성을 통해 데이타의 무결성을 보장해 줍니다.
- 원자성 : 트랜잭션 작업 단위는 전부 실행이 되던지 아니면 전부 실패되어야 합니다.
- 일관성 : 특정한 조건(데이타 길이, 데이타 타입, Null 여부 등)이 지정된 데이타는 도중에 데이타가 변조되지 않아야 합니다.
- 격리성 : 하나의 트랜잭션에서 다루고 있는 데이타는 동시에 다른 트랜잭션에서 사용할 수 없습니다.
- 영속성 : 트랜잭션이 성공적으로 완료된 데이타는 안정적으로 데이타베이스에 저장/관리 됩니다.
Spring 에서는 데이타소스에 지정된 트랜잭션 매니저를 이용해 트랜잭션을 사용할 수 있는데 @Transactional 이라는 어노테이션이 지정된 함수 범위 안에서 예기치 못한, 혹은 의도된 문제가 발생 되었을때 트랜잭션 매니저가 알아서 데이타를 롤백 처리를 진행해 줍니다.
이와 반대로 @Transactional 작업 단위가 정상적으로 끝난다면 트랜잭션 매니저가 commit 을 통해 데이타를 영속화 시킵니다.
@Transactional(rollbackFor = Exception.class)
public void execute() {
throw new RuntimeException("강제로 에러를 발생시켜 데이타를 롤백 !!");
}
다중 데이타소스에서 트랜잭션 처리의 어려움
하지만 다중 데이타 소스를 사용할때는 보다 복잡한 트랜잭션 처리를 해야 하는데 이것은 Spring 이 어떤 데이타 소스에서 트랜잭션을 처리해야 되는지를 알 수 없기 때문이죠. 만약 데이타소스가 2개이고 트랜잭션 매니저가 2개인데 @Transactional 을 지정했다면 @Primary 로 우선권을 가지는 트랜잭션 매니저가 실행이 됩니다. 결국 하나의 데이타는 정상적인 트랜잭션 범위안에 포함 될 수 없다는 이야기 입니다.
그렇기 때문에 다중 데이타 소스에서 트랜잭션 관리를 해야 한다면 데이타 소스에서 직접 Connnection 을 가지고 와서 수동으로 트랜잭션 처리를 해주는 방법이 있습니다만 이는 생각보다 소스 코드의 양도 많아지고 시점 관리가 어려워 지는 문제점이 있습니다.
public void execute() throws Exception {
Connection con1 = null, con2 = null;
try {
con1 = dataSource1.getConnection();
con2 = dataSource2.getConnection();
//뭔가를 실행
con1.commit();
con2.commit();
} catch (Exception e) {
con1.rollback();
con2.rollback();
} finally {
if (con1 != null) con1.close();
if (con2 != null) con2.close();
}
}
ChainedTransactionManager
이런 부분 때문에 다중 트랜잭션 처리를 위해 Spring 에서는 ChainedTransactionManager 라는 구현체를 제공해 주고 있는데요. 데이타 소스마다 수동으로 트랜잭션을 관리 해야 되는 어려움과 복잡함을 간단히 처리할 수 있게 기능을 제공하고 있습니다.
실제 구현되어 있는 내용을 분석해 보면 수동으로 트랜잭션을 처리하는 것과 크게 다르지 않습니다. 결국 내부적으로 사용되는 방식은 JDBC API 를 통해 데이타를 관리 되기 때문입니다.
좀더 자세히 ChainedTransactionManager 를 살펴 보면 객체 생성시 주입 받은 각각의 트랜잭션 매니저들을 List 에 담아 두고 있다가 Rollback 혹은 Commit 상황이 발생될때 트랜잭션 매니저를 담아두고 있는 List 를 반복 하면서 Rollback, Commit 처리를 일괄 진행해 줍니다.
public class ChainedTransactionManager implements PlatformTransactionManager {
...
public ChainedTransactionManager(PlatformTransactionManager... transactionManagers) {
this(SpringTransactionSynchronizationManager.INSTANCE, transactionManagers);
}
ChainedTransactionManager(SynchronizationManager synchronizationManager, PlatformTransactionManager... transactionManagers) {
...
this.transactionManagers = Arrays.asList(transactionManagers);
}
public void commit(TransactionStatus status) throws TransactionException {
...
Iterator var6 = this.reverse(this.transactionManagers).iterator();
while(var6.hasNext()) {
PlatformTransactionManager transactionManager = (PlatformTransactionManager)var6.next();
if (commit) {
try {
multiTransactionStatus.commit(transactionManager);
} catch (Exception var9) {
commit = false;
commitException = var9;
commitExceptionTransactionManager = transactionManager;
}
} else {
try {
multiTransactionStatus.rollback(transactionManager);
} catch (Exception var10) {
LOGGER.warn("Rollback exception (after commit) (" + transactionManager + ") " + var10.getMessage(), var10);
}
}
}
...
}
public void rollback(TransactionStatus status) throws TransactionException {
...
Iterator var5 = this.reverse(this.transactionManagers).iterator();
while(var5.hasNext()) {
PlatformTransactionManager transactionManager = (PlatformTransactionManager)var5.next();
try {
multiTransactionStatus.rollback(transactionManager);
} catch (Exception var8) {
if (rollbackException == null) {
rollbackException = var8;
rollbackExceptionTransactionManager = transactionManager;
} else {
LOGGER.warn("Rollback exception (" + transactionManager + ") " + var8.getMessage(), var8);
}
}
}
...
}
...
}
ChainedTransactionManager 설정
실질적으로 프로젝트에서 사용하기 위해 앞선 글에서 다중 데이타 소스를 생성할때 같이 생성한 트랜잭션 매니저들을 주입 받아서 ChainedTransactionManager 객체를 생성합니다.
이때 ChainedTransactionManager 에 @Primary 어노테이션을 지정하여 프로젝트에 가장 우선순위가 높은 대표 트랜잭션 매니저임을 지정해 둡니다.
실제 서비스에서 사용할때는 단일 데이타 소스를 사용할때와 동일하게 @Transactional 을 사용해 트랜잭션을 지정합니다. 이때 사용되는 트랜잭션 매니저는 ChainedTransactionManager 가 될 것이며 데이타 소스 종류와 상관없이 동일한 트랜잭션 단위로 묶이게 될 것입니다.
@Configuration
public class ChainedTxConfig {
@Bean
@Primary
public PlatformTransactionManager transactionManager(PlatformTransactionManager txManager1, PlatformTransactionManager txManager2) {
return new ChainedTransactionManager(txManager1, txManager2);
}
}
다음글에서는 최종적으로 테스트를 진행해 다중 데이타소스 상에서 정상적인 데이타 커밋과 롤백이 이루어지는지를 확인해 보겠습니다.
'개발 > JPA 다중 데이타소스 관리' 카테고리의 다른 글
JPA 다중 데이타소스 관리 (3) (2) | 2018.09.09 |
---|---|
JPA 다중 데이타소스 관리 (1) (0) | 2018.09.04 |