Spring的多数据源配置中会用到AbstractRoutingDataSource,从名字也可以看出他的作用,抽象的带路由的数据源,源码的说明如下:
* Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()}
* calls to one of various target DataSources based on a lookup key. The latter is usually
* (but not necessarily) determined through some thread-bound transaction context.
类的声明如下:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
......
}
那他是如何做到多数据源访问的呢,跟下代码,AbstractRoutingDataSource的getConnection方法:
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
determineTargetDataSource方法:
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();//获取key
DataSource dataSource = this.resolvedDataSources.get(lookupKey);//根据key获取真正的数据源,当然前提是resolvedDataSources有值,这也是子类实现中做的事情
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方法:
/**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* <p>Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
@Nullable
protected abstract Object determineCurrentLookupKey();
注意这是一个抽象方法,是留给子类实现具体的选择数据源的方法,从方法声明中知道返回的是一个的lookupkey,即数据源的key。
那子类应该如何写呢,以若依框架中的多数据源配置为例:
package com.ruoyi.framework.datasource;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源:继承在AbstractRoutingDataSource,并设置了父类的targetDataSources,决定使用数据源的key
* @author ruoyi
*/
public class DynamicDataSource extends AbstractRoutingDataSource
{
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
{
super.setDefaultTargetDataSource(defaultTargetDataSource);
//设置父类的targetDataSources,供获取connection时选择使用哪一个数据源
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
//决定使用的数据源的key
protected Object determineCurrentLookupKey()
{
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
package com.ruoyi.framework.datasource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 数据源切换处理
*
* @author ruoyi
*/
public class DynamicDataSourceContextHolder
{
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDataSourceType(String dsType)
{
log.info("切换到{}数据源", dsType);
CONTEXT_HOLDER.set(dsType);
}
/**
* 获得数据源的变量
*/
public static String getDataSourceType()
{
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType()
{
CONTEXT_HOLDER.remove();
}
}
此类借助于ThreadLocal用于设置和获取具体的数据源的key。