비즈니스 메서드를 리팩터링 하는 와중에 아래와 같은 고민이 생겼습니다.
@Trasactional(propagtaion=Propagation.REQUIRED_NEW, rollbackFor = Exception.class)
public void method(){
readOnlyTxMethod(); // @Transactional(readOnly = true)
writeTxMethod(); // @Transactional(rollbackFor = Exception.class)
}
method()에 지정된 @Transactional으로 인해 read-only Tx와 write Tx가 하나로 묶여 있어
하나의 트랜잭션의 범위가 너무 넓어졌습니다.
read-only Tx와 write Tx는 서로 상호 배타적이면서 연동되는 데이터베이스도 다르기 때문에
트랜잭션을 분리하는 것이 맞다고 생각되었습니다.
public void method(){
readOnlyTxMethod(); // @Transactional(readOnly = true)
writeTxMethod(); // @Transactional(rollbackFor = Exception.class)
}
여기서 고민해볼 것이 readOnly = true와 readOnly = false의 트랜잭션이 별개로 생성된다고는 들었지만
과연 맞을까? 라는 의문이 들었고 트랜잭션이 동일한지 판단이 필요했습니다.
트랜잭션 매니저 클래스를 DataSourceTransactionManager를 사용하고 있습니다.
이 클래스는 트랜잭션을 관리하는 클래스이며 계층 구조가 다음과 같습니다.
CciLocalTransactionManager, HibernateTransactionManager, JpaTransacationManager... 모두 추상 클래스인 AbstractPlatformTransactionManager를 상속받습니다.
트랜잭션이 시작되면 AbstractPlatformTransactionManager에서 트랜잭션을 획득하고자 getTransaction()이 호출됩니다.
이 메서드는 다양한 조건에 맞는 트랜잭션을 반환합니다.
아래는 getTransaction() 코드 전문입니다.
중단점을 이용하여 'if(this.isExistingTransaction(transaction))' 이 조건을 만족하여 기존 트랜잭션이 반환되는지 여부와
else 구문이 실행되어 새로운 트랜잭션이 생성 여부가 관건이며 readOnlyTxMethod() , writeTxMethod()가 별개의 트랜잭션으로 수행되는 것을 확인했습니다.
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
Object transaction = this.doGetTransaction();
boolean debugEnabled = this.logger.isDebugEnabled();
if (definition == null) {
definition = new DefaultTransactionDefinition();
}
if (this.isExistingTransaction(transaction)) {
return this.handleExistingTransaction((TransactionDefinition)definition, transaction, debugEnabled);
} else if (((TransactionDefinition)definition).getTimeout() < -1) {
throw new InvalidTimeoutException("Invalid transaction timeout", ((TransactionDefinition)definition).getTimeout());
} else if (((TransactionDefinition)definition).getPropagationBehavior() == 2) {
throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
} else if (((TransactionDefinition)definition).getPropagationBehavior() != 0 && ((TransactionDefinition)definition).getPropagationBehavior() != 3 && ((TransactionDefinition)definition).getPropagationBehavior() != 6) {
if (((TransactionDefinition)definition).getIsolationLevel() != -1 && this.logger.isWarnEnabled()) {
this.logger.warn("Custom isolation level specified but no actual transaction initiated; isolation level will effectively be ignored: " + definition);
}
boolean newSynchronization = this.getTransactionSynchronization() == 0;
return this.prepareTransactionStatus((TransactionDefinition)definition, (Object)null, true, newSynchronization, debugEnabled, (Object)null);
} else {
SuspendedResourcesHolder suspendedResources = this.suspend((Object)null);
if (debugEnabled) {
this.logger.debug("Creating new transaction with name [" + ((TransactionDefinition)definition).getName() + "]: " + definition);
}
try {
boolean newSynchronization = this.getTransactionSynchronization() != 2;
DefaultTransactionStatus status = this.newTransactionStatus((TransactionDefinition)definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
this.doBegin(transaction, (TransactionDefinition)definition);
this.prepareSynchronization(status, (TransactionDefinition)definition);
return status;
} catch (RuntimeException var7) {
this.resume((Object)null, suspendedResources);
throw var7;
} catch (Error var8) {
this.resume((Object)null, suspendedResources);
throw var8;
}
}
}
'[개발] 프레임워크 > Spring' 카테고리의 다른 글
Spring AOP (1) | 2024.06.26 |
---|---|
Spring MVC (0) | 2024.06.23 |
Actuator (0) | 2023.12.26 |
Swagger (0) | 2023.12.26 |
[MyBatis] TypeHandler (0) | 2023.12.12 |