[#7] TransactionManager가 DataSource정하는 로직을 늦추기 - LazyConnectionDataSourceProxy
tjdrnr05571.tistory.com/14?category=876333
위 링크에서처럼 Mysql Replication을 Spring에서 구현하던 중 @Transactional로 트랜잭션을 시작 시켜줄 때 DataSource가 무조건 기본 Master DataSource로만 쿼리가 가는 문제가 생겼습니다. 이는 스프링에서 트랜잭션을 시작할 때 바로 DataSource를 정하고 그 DataSource로 트랜잭션 메소드 내 모든 쿼리 수행을 하기 때문입니다. 현재 저의 프로젝트에서는 AOP로 쿼리 메소드에 @SetDataSource로 마스터나 슬레이브 데이터소스를 정해주는 로직이기 때문에 위의 문제가 발생하면 무조건 Master DataSource로만 쿼리가 가는 문제가 발생했던 것입니다.
1. Spring은 기본적으로 트랜잭션을 시작할 때 쿼리가 실행되기도 전에 DataSource를 정해놓습니다.
(TransactionManager 식별 -> DataSource에서 Connection 가져오고 -> Transaction 동기화(Synchronization)
2. Transaction이 시작되면 같은 DataSource만을 이용합니다.
@SetDataSource(dataSourceType = DataSourceType.SLAVE)
UserDTO selectUserById(String id);
@SetDataSource(dataSourceType = DataSourceType.MASTER)
void deleteUser(String id);
따라서 위 스샷처럼 쿼리 메소드에 AOP로 DataSource를 정하는 로직이 가능하게 하려면 쿼리를 실행할 때 DataSource를 정할 수 있도록
DataSource 연결을 늦춰주도록 구현하여야 합니다. 이는 밑 스샷과 같이 LazyConnectionDataSourceProxy를 이용하여 현재 RoutingDataSource를 감싸서 구현 가능합니다.
@Bean
public DataSource lazyRoutingDataSource(
@Qualifier(value = "routingDataSource") DataSource routingDataSource) {
return new LazyConnectionDataSourceProxy(routingDataSource);
}
@Bean
public PlatformTransactionManager transactionManager(
@Qualifier(value = "lazyRoutingDataSource") DataSource lazyRoutingDataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(lazyRoutingDataSource);
return transactionManager;
}
routingDataSource를 LazyConnectionDataSourceProxy로 감싸면
TransactionManager 식별 -> LazyConnectionDataSourceProxy에서 Connection Proxy 객체 획득 -> Transaction 동기화(Synchronization) -> 실제 쿼리 호출시에 RoutingDataSource.getConnection()/determineCurrentLookupKey() 호출 과 같이 트랜잭션 매니저의 로직이 변경됩니다.
또한 routingDataSource의 빈 이름이 DataSource가 아니라면 TransactionManager에 DataSource를 새로 지정해주어야합니다.
이렇게 설정을 변경해준다면 @Transactional 이 걸린 메소드에서 ReadOnly 설정을 해주면 Master 데이터소스를 사용하고 설정을 해주지않으면 Slave 데이터소스를 사용하도록 다음과 같이 변경해준다면 트랜잭션내 실제 쿼리가 실행되는 메소드일 때 트랜잭션의 상태를 파악해서 데이터소스를 정해줍니다.
AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
@Override
protected Object determineCurrentLookupKey() {
DataSourceType dataSourceType = RoutingDataSourceManager.getCurrentDataSourceName();
if (TransactionSynchronizationManager
.isActualTransactionActive()) {
boolean readOnly = TransactionSynchronizationManager
.isCurrentTransactionReadOnly();
if (readOnly) {
dataSourceType = DataSourceType.SLAVE;
} else {
System.out.println("not-readOnly");
dataSourceType = DataSourceType.MASTER;
}
}
RoutingDataSourceManager.removeCurrentDataSourceName();
return dataSourceType;
}
프로젝트 url
github.com/f-lab-edu/make-delivery
참고
egloos.zum.com/kwon37xi/v/5364167
d2.naver.com/helloworld/5812258