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。