-
[Spring] multi datasource 동적으로 사용 - AbstractRoutingDataSourceSpring/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에 저장할 수 있다.
AbstractRoutingDataSource 의 determineCurrentLooupKey()를 오버라이드해서 상황에 맞게 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 정보를 다르게 사용할 수 있다.
반응형'Spring > spring boot 및 기타' 카테고리의 다른 글
[spring boot] 스프링 부트 2.1 변경 내역 (0) 2021.08.31 [spring] Mybatis batch upsert 하는 방법(mysql) (0) 2021.08.11 [spring boot] mybatis 연동 및 설정 (0) 2021.06.23 [spring boot] 스프링 firebase database 사용하기 (1) 2021.05.04 [spring boot] 스프링 부트 h2 인메모리 db 시작하기 (0) 2021.04.27