ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring] multi datasource 동적으로 사용 - AbstractRoutingDataSource
    Spring/spring boot 및 기타 2021. 8. 3. 01:19

    사용이유

    datasource 커넥션을 로직이나 상황별로 나눠야 하는 상황이 올 수 도 있고, 어플리케이션단에서 샤딩을 구현해야 할 수 도 있고, read 커넥션 및 write 커넥션의 datasource 를 나눠야 하는 경우가 있다.

    애초에 다른 datasoure 파일을 만들어도 되지만, 동적으로 쿼리 요청시마다 datasource 를 변경해야 할 때 AbstractRoutingDataSource 를 사용하면 된다.

     

    AbstractRoutingDataSource란?

    spring-jdbc 모듈에 포함되어 있는 클래스로, 여러 DataSource를 등록하고 특정 상황에 맞게 원하는 DataSource를 사용할 수 있도록 추상화한 클래스이다.

     

    구현방식

    AbstractRoutingDataSource는 이름 기반으로 다중 DataSource 정보를 등록하는 방법이다.

    AbstractRoutingDataSource의

    public void setTargetDataSources(Map<object, object=""> targetDataSource)를 호출하여

    String:DataSource을 키:값으로하는 Datasource를 Map에 저장할 수 있다.

     

    AbstractRoutingDataSourcedetermineCurrentLooupKey()를 오버라이드해서 상황에 맞게 Key를 반환하도록 구현해야 한다.

    어떤 데이터 소스를 쓸지에 대한 결정은 determineCurrentLooupKey 메소드 내부에서 쿼리 호출시마다 동적으로 결정된다.

    return 되는 String 결과값에 해당되는 datasource 를 실행하게 된다.

    public class MyRoutingDataSource extends AbstractRoutingDataSource {
    	@Override
    	protected Object determineCurrentLookupKey() {
            boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
            if (isReadOnly) {
                return "slave";
            } else {
                return "master";
            }	
    	}
    }

    참고로 boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); 를 사용하려면

    transactionManager 에 LazyConnectionDataSourceProxy 를 설정해 주어야 한다.

        @Bean("RoutingDataSource")
        public DataSource routingDataSource() {
            HikariDataSource masterDataSource = createDatasource(~~);
            HikariDataSource slaveDataSource = createDatasource(~~);
    
            Map<Object, Object> dataSourceMap = new HashMap<>();
            dataSourceMap.put("master", masterDataSource);
            dataSourceMap.put("slave", slaveDataSource);
    
            DynamicRoutingDataSource routingDataSource = new MyRoutingDataSource();
            routingDataSource.setTargetDataSources(dataSourceMap);
            routingDataSource.setDefaultTargetDataSource(masterDataSource);
            return routingDataSource;
        }
        
        @Bean("routingLazyDataSource")
        public DataSource routingLazyDataSource(@Qualifier("RoutingDataSource") DataSource dataSource) {
            return new LazyConnectionDataSourceProxy(dataSource);
        }
        
        @Bean(name = "transactionManager")
        public PlatformTransactionManager transactionManager(@Qualifier("routingLazyDataSource") DataSource dataSource) {
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
            transactionManager.setDataSource(dataSource);
            return transactionManager;
        }
        
        public HikariDataSource createDatasource() {
        	
            // 데이터 connection 정보
            ~~
            return new HikariDataSource(config);
        }

     

    LazyConnectionDataSourceProxy 는 데이터소스를 한번 더 감싸는 역할을 해주어, LazyConnectionDataSourceProxy 로 여러 데이터 소스를 묶어 PlatformTransactionManager 에 datasource 를 세팅해야, tranaction 이 정확히 동작한다.

    다음과 같이 PlatformTransactionManager 에 LazyConnectionDataSourceProxy 로 datasource 여러개를 감싼 datasource 를 set 해줘야 

    TransactionSynchronizationManager.isCurrentTransactionReadOnly() 를 할 경우 정확히 readOnly 를 사용하는 쿼리를 캐치해서 분리 할 수 있다.

    @Transactional(readOnly = true)
    public List<Test> findTestList() {
    	return testMapper.selectTestList();
    }

    다음과 같이 readOnly 가 true 인 경우는 isCurrentTransactionReadOnly 가 true 이므로 determineCurrentLookupKey 메소드에서 "slave" datasource 를 반환하게 된다.

    만약에 위에서 만든 여러 datasource 를 LazyConnectionDataSourceProxy 로 감싸지 않으면 readOnly 가 true 인지를 알 수 가 없다.

     

     

    위처럼도 사용가능하고, determineCurrentLookupKey 를 어떻게 구현하느냐에 따라 db 정보를 다르게 사용할 수 있다. 

     

    반응형

    댓글

Designed by Tistory.