Spring多数据源配置
Spring实现多数据源的配置主要依靠继承AbstractRoutingDataSource来实现
AbstractRoutingDataSource
- 首先附上AbstractRoutingDataSource的类图结构
- AbstractRoutingDataSource的代码实现
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
@Nullable
private Map<Object, Object> targetDataSources;
@Nullable
private Object defaultTargetDataSource;
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
private DataSource resolvedDefaultDataSource;
public AbstractRoutingDataSource() {
}
//省略set方法...
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
} else {
this.resolvedDataSources = new HashMap(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);
}
}
}
protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
}
protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if (dataSource instanceof DataSource) {
return (DataSource)dataSource;
} else if (dataSource instanceof String) {
return this.dataSourceLookup.getDataSource((String)dataSource);
} else {
throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
}
public Connection getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return this.determineTargetDataSource().getConnection(username, password);
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return iface.isInstance(this) ? this : this.determineTargetDataSource().unwrap(iface);
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return iface.isInstance(this) || this.determineTargetDataSource().isWrapperFor(iface);
}
//找到目标数据源
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = this.determineCurrentLookupKey();
DataSource 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 + "]");
} else {
return dataSource;
}
}
// 重点方法主要是通过这个方法寻找resolvedDataSources中已经存放的数据源
@Nullable
protected abstract Object determineCurrentLookupKey();
}
- determineTargetDataSource核心方法,通过子类重写的determineCurrentLookupKey()返回已处理数据源map中对应的数据源
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
//通过当前lookupKey获取数据源(靠自定义实现类决定如何寻找)
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
//如果数据源为空,开启了宽松模式或者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;
}
- determineTargetDataSource()决定好数据源,将数据源返回给jdbc获取连接
public Connection getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return this.determineTargetDataSource().getConnection(username, password);
}
- 当成员变量属性赋值后,会调用这个方法,这个方法会把传进来的多数据源转化为已处理的数据源,然后配置一个兜底的默认数据源
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
} else {
this.resolvedDataSources = new HashMap(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);
}
}
}
配合着ThreadLocal的Demo使用
- 自定义的DynamicDataSource 继承 AbstractRoutingDataSource并重写determineCurrentLookupKey()
public class DynamicDataSource extends AbstractRoutingDataSource {
private Map<Object, Object> datasources;
public DynamicDataSource() {
datasources = new HashMap<>();
super.setTargetDataSources(datasources); //要放一个数据源,初始化,后面可以再set
}
public <T extends DataSource> void addDataSource(DataSourceKey key, T data) {
datasources.put(key, data);
}
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDataSourceKey();
}
}
- DataSourceHolder的定义
//使用ThreadLocal,实现不同的线程获取不同的数据源连接
private static final ThreadLocal<DataSourceKey> dataSourceKey = new ThreadLocal<>();
//得到当前的数据库连接
public static DataSourceKey getDataSourceKey() {
return dataSourceKey.get();
}
//设置当前的数据库连接
public static void setDataSourceKey(DataSourceKey type) {
dataSourceKey.set(type);
}
//清除当前的数据库连接
public static void clearDataSourceKey() {
dataSourceKey.remove();
}
- 建议动态配置类,将Bean放入ioc容器中
@AutoConfigureBefore(value={DruidDataSourceAutoConfigure.class,MybatisPlusAutoConfiguration.class})
@ConditionalOnProperty(name = {"spring.datasource.dynamic.enable"}, matchIfMissing = false, havingValue = "true")
public class DataSourceAutoConfig {
// 创建数据源
@Bean
@ConfigurationProperties("spring.datasource.druid.core")
public DataSource dataSourceCore(){
return DruidDataSourceBuilder.create().build();
}
// 所有的核心库共享一个日志中心模块,该模块不采用mysql中的innodb引擎,采用归档引擎
@Bean
@ConfigurationProperties("spring.datasource.druid.log") //通过反射注入值
public DataSource dataSourceLog(){
return DruidDataSourceBuilder.create().build();
}
@Primary //赋予bean更高的优先级
@Bean // 只需要纳入动态数据源到spring容器
public DataSource dataSource() {
DynamicDataSource dataSource = new DynamicDataSource();
DataSource coreDataSource = dataSourceCore() ;
DataSource logDataSource = dataSourceLog();
dataSource.addDataSource(DataSourceKey.core, coreDataSource);
dataSource.addDataSource(DataSourceKey.log, logDataSource);
dataSource.setDefaultTargetDataSource(coreDataSource);
return dataSource;
}
@Bean // 将数据源纳入spring事务管理
public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}