보통의 경우에는 애플리케이션에서 하나의 DB에만 조회를 한다.
하지만 DB 부하를 분산하기 위해서 조회는 Slave에서만 한다던가, 샤딩된 DB를 사용하기도 한다.
이러한 경우에 어떻게 MultiDataSource를 조회할 수 있도록 Spring에서 설정할 수 있는지 알아보자.
DataSource 정보
application.yml을 이용하여 DB url 및 user 정보 관리
multi-datasource:
kakao:
url: jdbc:h2:tcp://localhost/~/testcase
username: sa
passwd:
naver:
url: jdbc:h2:tcp://localhost/~/test
username: sa
passwd:
위의 설정 정보를 매핑할 DataSource Bean 설정
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("multi-datasource.kakao")
public DataSourceProperties kProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties("multi-datasource.naver")
public DataSourceProperties nProperties() {
return new DataSourceProperties();
}
public DataSource kDataSource() {
return kProperties()
.initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
}
public DataSource nDataSource() {
return nProperties()
.initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
}
}
이제 여러 개의 DataSource를 사용할 준비가 되었다.
어떻게 사용하는지 알아보자.
AbstractRoutingDataSource
Spring에서 다중의 DataSource를 사용할 수 있도록 추상 클래스를 제공해 준다.
해당 추상 클래스의 내용을 보면 defaultTargetDataSource, targetDataSources를 지정해주어야 한다.
targetDataSources가 Map으로 value에 DataSource를 가지고 있게 된다.
그러면, 원하는 value를 찾기 위해서 key가 필요할 텐데
key를 찾는 구현부는 각 요구사항마다 다를 테니 abstract으로 열려있다.
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceKeyHolder.getDataSourceKey();
}
}
이제 위의 RoutingDataSource를 Bean으로 등록해 주자.
DataSourceConfig에 추가해 주면 된다.
@Bean
public DataSource dataSource() {
RoutingDataSource ds = new RoutingDataSource();
ds.setDefaultTargetDataSource(kDataSource());
ds.setTargetDataSources(targetDataSource());
return ds;
}
private Map<Object, Object> targetDataSource() {
HashMap<Object, Object> target = new HashMap<>();
target.put("kakao", kDataSource());
target.put("naver", nDataSource());
return target;
}
RoutingDataSource의 구현 내용으로는 DataSourceKeyHolder에 ThreadLocal에 그 값을 저장하고 필요할 때마다 꺼내 쓰게 할 수 있다.
값을 넣는 시점은 각 요청사항마다 다를 것이다.
ex) http 요청이 들어왔을 때, transaction을 시작하기 전,,,
public class DataSourceKeyHolder {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static String getDataSourceKey() {
return threadLocal.get();
}
public static void setDataSourceKey(String key) {
threadLocal.set(key);
}
public static void clearDataSourceKey() {
threadLocal.remove();
}
}
@GetMapping("/test")
public void test() {
DataSourceKeyHolder.setDataSourceKey("naver");
testService.test();
}