因为配置多数据源使用了这个类,固记录一下,方便后面查阅
ThreadLocalRountingDataSource继承了AbstractRoutingDataSource,实现其抽象方法protected abstract Object determineCurrentLookupKey(); 从而实现对不同数据源的路由功能。我们从源码入手分析下其中原理:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
AbstractRoutingDataSource 实现了 InitializingBean 那么spring在初始化该bean时,会调用InitializingBean的接口 void afterPropertiesSet() throws Exception; 我们看下AbstractRoutingDataSource是如何实现这个接口的:
@Override public void afterPropertiesSet() { if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); this.resolvedDataSources.put(lookupKey, dataSource); } if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); } }
targetDataSources 是我们在xml配置文件中注入的 dataSourceMaster 和 dataSourceSlave. afterPropertiesSet方法就是使用注入的 dataSourceMaster 和 dataSourceSlave来构造一个HashMap——resolvedDataSources。方便后面根据 key 从该map 中取得对应的dataSource。 我们在看下 AbstractDataSource 接口中的 Connection getConnection() throws SQLException; 是如何实现的:
@Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); }
关键在于 determineTargetDataSource(),根据方法名就可以看出,应该此处就决定了使用哪个 dataSource :
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; }
Object lookupKey = determineCurrentLookupKey(); 该方法是我们实现的,在其中获取ThreadLocal中保存的 key 值。获得了key之后, 在从afterPropertiesSet()中初始化好了的resolvedDataSources这个map中获得key对应的dataSource。而ThreadLocal中保存的 key 值 是通过AOP的方式在调用service中相关方法之前设置好的。OK,到此搞定!
另外在配置多数据源时,有一个注意点,不同的AOP注解的执行顺序的问题,即aop拦切换数据源注解@before必须在事务注解之前执行,否则会导致数据源不能正常切换,最主要的还是因为如果先执行事务的话,DataSourceTransactionManager会创建一个ConnectinoHolder,当前线程共享这个ConnectionHolder,TransactionManager在做完doBegin和prepareSynchronization准备工作后,开始执行切数据源的Aspect,这时是可以正确切换的,问题是接下来SqlSessionTemplapate(BaseExecutor通过mybatis自己的事务)在执行sql语句时并不是直接用dataSource的Connection,而是通过SpringManagedTransaction从DataSourceUtils#getConnection(this.dataSource) 中获取Connection。DataSourceUtils中会先从当前线程中获取ConnectinoHolder,获取不到才从DataSource中获取!结果就是执行sql语句时使用了错误的Connection去执行!