多数据源
大体思路:
多数据源配置通过Aop + 自定义注解 + 实现AbstractRoutingDataSource接口
实现原理:spring是通过阅读AbstractRoutingDataSource获取数据源连接,阅读**getConnection()**方法,可知通过determineTargetDataSource() 获取具体的数据源
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
determineTargetDataSource() 获取数据的源码如下:通过determineCurrentLookupKey()
的返回值获取数据源、获取不到则使用默认数据源、两者皆不存在则抛出异常IllegalStateException。
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
关键来到了determineCurrentLookupKey()
在AbstractRoutingDataSource中 这个方法是抽象方法、所以一直是使用默认的数据源、想要动态的变更数据源、咱们可以自定义一个实现类实现该方法、
@Nullable
protected abstract Object determineCurrentLookupKey();
注意 AbstractRoutingDataSource 源码会将targetDatasouce 处理成resolveDatasource
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
} else {
this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
Object lookupKey = this.resolveSpecifiedLookupKey(key);
DataSource dataSource = this.resolveSpecifiedDataSource(value);
this.resolvedDataSources.put(lookupKey, dataSource);
});
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
}
实现步骤/核心代码
- 首先通过 @ConfigurationProperties 读取配置文件,并将多个数据源转换为Map<String, DataSourceProperties>
##多数据源的配置
#dynamic:
datasource:
slave1:
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://localhost:1433;DatabaseName=renren_security
username: sa
password: 123456
slave2:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/renren_security
username: renren
password: 123456
@ConfigurationProperties(prefix = "dynamic")
public class DynamicDataSourceProperties {
private Map<String, DataSourceProperties> datasource = new LinkedHashMap<>();
public Map<String, DataSourceProperties> getDatasource() {
return datasource;
}
public void setDatasource(Map<String, DataSourceProperties> datasource) {
this.datasource = datasource;
}
}
- 其次通过实现AbstractRoutingDataSource 接口实现
determineCurrentLookupKey()
方法
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
//在DynamicContextHolder获取当前数据源
return DynamicContextHolder.peek();
}
}
- 将自定义的DynamicDataSource动态数据源注入到spring中,并初始化targetDataSources以及defaultTargetDataSource属性
@Bean
public DynamicDataSource dynamicDataSource(DataSourceProperties dataSourceProperties) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//将自己的动态数据源添加到系统
dynamicDataSource.setTargetDataSources(getDynamicDataSource());
//默认数据源
DruidDataSource defaultDataSource = DynamicDataSourceFactory.buildDruidDataSource(dataSourceProperties);
dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
return dynamicDataSource;
}
// 将配置文件中的动态数据源组装成datasouce对象部分代码
private Map<Object, Object> getDynamicDataSource(){
Map<String, DataSourceProperties> dataSourcePropertiesMap = properties.getDatasource();
Map<Object, Object> targetDataSources = new HashMap<>(dataSourcePropertiesMap.size());
dataSourcePropertiesMap.forEach((k, v) -> {
DruidDataSource druidDataSource = DynamicDataSourceFactory.buildDruidDataSource(v);
targetDataSources.put(k, druidDataSource);
});
return targetDataSources;
}
- 自定义注解**@DataSource**并通过Aop拦截处理设置当前的数据源,将注解对应的值放到determineCurrentLookupKey()做返回,DynamicDataSource通过lookupKey选择具体的数据源获取连接。
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DataSourceAspect {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(io.renren.datasource.annotation.DataSource) " +
"|| @within(io.renren.datasource.annotation.DataSource)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Class targetClass = point.getTarget().getClass();
Method method = signature.getMethod();
DataSource targetDataSource = (DataSource)targetClass.getAnnotation(DataSource.class);
DataSource methodDataSource = method.getAnnotation(DataSource.class);
if(targetDataSource != null || methodDataSource != null){
String value;
if(methodDataSource != null){
value = methodDataSource.value();
}else {
value = targetDataSource.value();
}
//将当前数据源放入DynamicContextHolder
DynamicContextHolder.push(value);
logger.debug("set datasource is {}", value);
}
try {
return point.proceed();
} finally {
DynamicContextHolder.poll();
logger.debug("clean datasource");
}
}
}
备注
实现代码可参考人人开源:https://gitee.com/renrenio/renren-fast.git