关于Spring3 + Mybatis3整合时多数据源动态切换的问题

以前的项目经历中,基本上都是spring + hibernate + Spring JDBC这种组合用的多。至于MyBatis,也就这个项目才开始试用,闲话不多说,进入正题。

以前的这种框架组合中,动态数据源切换可谓已经非常成熟了,网上也有非常多的博客介绍,都是继承AbstractRoutingDataSource,重写determineCurrentLookupKey()方法。具体做法就不在此废话了。

所以当项目中碰到这个问题,我几乎想都没有想,就采用了这种做法,但是一测试,一点反应都没有。当时觉得不可能,于是断点,加log调试,发现determineCurrentLookupKey()根本没有调用。

为什么列? 这不可能啊。静下心来,仔细想想,才想到一个关键的问题: Mybatis整合Spring,而不是Spring整合的Mybatis! 直觉告诉我,问题就出在这里。

于是花时间去研究一下mybatis-spring.jar 这个包,发现有SqlSession这东西,很本能的就注意到了这一块,然后大致看一下他的一些实现类。于是就发现了他的实现类里面有一个内部类SqlSessionInterceptor(研究过程就不多说了,毕竟是个痛苦的过程)

好吧,这个类的作用列,就是产生sessionProxy。关键代码如下

final SqlSession sqlSession = getSqlSession(
 SqlSessionTemplate.this.sqlSessionFactory,
 SqlSessionTemplate.this.executorType,
 SqlSessionTemplate.this.exceptionTranslator); 

这个sqlSessionFactory 我们就很眼熟啦,是我们在spring配置文件中配了的,是吧,也就是说这东西是直接从我们配置文件中读进来,但这东西,就关联了Datasource。所以就想到,如果能把这东西,做到动态,那么数据源切换,也就动态了。

于是第一反应就是写了一个类,然后在里面定义一个Map,用来存放多个SqlSessionFactory,并采用Setter方法进行属性注入。

public class EjsSqlSessionTemplate extends SqlSessionTemplate { 

 private Map<String, SqlSessionFactory> targetSqlSessionFactory = new HashMap<String, SqlSessionFactory>();
 public void setTargetSqlSessionFactory(Map<String, SqlSessionFactory> targetSqlSessionFactory) {
  this.targetSqlSessionFactory = targetSqlSessionFactory;
 } 

所以Spring的配置文件就变成了这样:

<bean id="sqlSessionTemplate" class="com.ejushang.spider.datasource.EjsSqlSessionTemplate">
  <constructor-arg ref="sqlSessionFactory" />
  <property name="targetSqlSessionFactory">
   <map>
    <entry value-ref="sqlSessionFactory" key="spider"/>
    <entry value-ref="sqlSessionFactoryTb" key="sysinfo"/>
   </map>
  </property>
 </bean>
 <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="basePackage" value="com.foo.bar.**.mapper*" />
  <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>
 </bean> 

那么这个思想是那里来的列? 当然就是借鉴了Spring的动态数据源的思想啦,对比一下Spring动态数据源的配置,看看是不是差不多?

然后重写了个关键的方法:

/**
  * 重写得到SqlSessionFactory的方法
  * @return
  */
 @Override
 public SqlSessionFactory getSqlSessionFactory() { 

  SqlSessionFactory targetSqlSessionFactory = this.targetSqlSessionFactory.get(SqlSessionContextHolder.getDataSourceKey());
  if (targetSqlSessionFactory != null) {
   return targetSqlSessionFactory;
  } else if ( this.getSqlSessionFactory() != null) {
   return this.getSqlSessionFactory();
  }
  throw new IllegalArgumentException("sqlSessionFactory or targetSqlSessionFactory must set one at least");
 } 

而SqlSessionContextHolder就很简单,就是一个ThreadLocal的思想

public class SqlSessionContextHolder {
 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
 private static Logger logger = LoggerFactory.getLogger(SqlSessionContextHolder.class);
 public static void setSessionFactoryKey(String dataSourceKey) {
  contextHolder.set(dataSourceKey);
 }
 public static String getDataSourceKey() {
  String key = contextHolder.get();
  logger.info("当前线程Thread:"+Thread.currentThread().getName()+" 当前的数据源 key is "+ key);
  return key;
 }
} 

博主信心满满就开始测试了。。结果发现不行,切换不过来,始终都是绑定的是构造函数中的那个默认的sqlSessionFactory,当时因为看了一天源码,头也有点晕。其实为什么列?

看看我们产生sessionProxy的地方代码,他的sqlSessionFactory是直接从构造函数来拿的。而构造函数中的sqlSessionFactory在spring容器启动时,就已经初始化好了,这点也可以从我们Spring配置文件中得到证实。

那这个问题,怎么解决列? 于是博主便想重写那个sqlSessionInterceptor。 擦,问题就来了,这个类是private的,没办法重写啊。于是博主又只能在自己的EjsSqlSessionTemplate类中,也定义了一个内部类,把源码中的代码都copy过来,唯一不同的就是我不是读取构造函数中的sqlSessionFactory.而是每次都去调用 getSqlSessionFactory()方法。代码如下:

final SqlSession sqlSession = getSqlSession(
EjsSqlSessionTemplate.this.getSqlSessionFactory(),
EjsSqlSessionTemplate.this.getExecutorType(),
EjsSqlSessionTemplate.this.getPersistenceExceptionTranslator()); 

再试,发现还是不行,再找原因,又回归到了刚才那个问题。因为我没有重写SqlSessionTemplate的构造函数,而sqlSessionProxy是在构函数中初始化的,代码如下:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
 PersistenceExceptionTranslator exceptionTranslator) {
 notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
 notNull(executorType, "Property 'executorType' is required");
 this.sqlSessionFactory = sqlSessionFactory;
 this.executorType = executorType;
 this.exceptionTranslator = exceptionTranslator;
 this.sqlSessionProxy = (SqlSession) newProxyInstance(
  SqlSessionFactory.class.getClassLoader(),
  new Class[] { SqlSession.class },
  new SqlSessionInterceptor());
} 

而SqlSessionInterceptor()这东西都是private。 所以父类压根就不会加载我写的那个SqlSessionInterceptor()。所以问题就出在这,那好吧,博主又重写构函数

public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
  super(getSqlSessionFactory(), executorType, exceptionTranslator);
 } 

很明显这段代码是编译不通过的,构造函数中,怎么可能调用类实例方法列?  那怎么办列? 又只有把父类的构造函数copy过来,那问题又有了,这些成员属性又没有。那又只得把他们也搬过来。。  后来,这个动态数据数据源的功能,终于完成了。

--------------------------------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------------------------整个完整的代码如下:

1、重写SqlSessionTemplate (重写的过程已经在上面分析过了)

public class EjsSqlSessionTemplate extends SqlSessionTemplate {
 private final SqlSessionFactory sqlSessionFactory;
 private final ExecutorType executorType;
 private final SqlSession sqlSessionProxy;
 private final PersistenceExceptionTranslator exceptionTranslator;
 private Map<Object, SqlSessionFactory> targetSqlSessionFactory;
 public void setTargetSqlSessionFactory(Map<Object, SqlSessionFactory> targetSqlSessionFactory) {
  this.targetSqlSessionFactory = targetSqlSessionFactory;
 }
 public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
 }
 public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
  this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration()
    .getEnvironment().getDataSource(), true));
 }
 public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
         PersistenceExceptionTranslator exceptionTranslator) {
  super(sqlSessionFactory, executorType, exceptionTranslator);
  this.sqlSessionFactory = sqlSessionFactory;
  this.executorType = executorType;
  this.exceptionTranslator = exceptionTranslator;
  this.sqlSessionProxy = (SqlSession) newProxyInstance(
    SqlSessionFactory.class.getClassLoader(),
    new Class[] { SqlSession.class },
    new SqlSessionInterceptor());
 }
 @Override
 public SqlSessionFactory getSqlSessionFactory() {
  SqlSessionFactory targetSqlSessionFactory = this.targetSqlSessionFactory.get(SqlSessionContextHolder.getDataSourceKey());
  if (targetSqlSessionFactory != null) {
   return targetSqlSessionFactory;
  } else if ( this.sqlSessionFactory != null) {
   return this.sqlSessionFactory;
  }
  throw new IllegalArgumentException("sqlSessionFactory or targetSqlSessionFactory must set one at least");
 }
 @Override
 public Configuration getConfiguration() {
  return this.getSqlSessionFactory().getConfiguration();
 }
 public ExecutorType getExecutorType() {
  return this.executorType;
 }
 public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
  return this.exceptionTranslator;
 }
 /**
  * {@inheritDoc}
  */
 public <T> T selectOne(String statement) {
  return this.sqlSessionProxy.<T> selectOne(statement);
 }
 /**
  * {@inheritDoc}
  */
 public <T> T selectOne(String statement, Object parameter) {
  return this.sqlSessionProxy.<T> selectOne(statement, parameter);
 }
 /**
  * {@inheritDoc}
  */
 public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
  return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey);
 }
 /**
  * {@inheritDoc}
  */
 public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
  return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey);
 }
 /**
  * {@inheritDoc}
  */
 public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
  return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey, rowBounds);
 }
 /**
  * {@inheritDoc}
  */
 public <E> List<E> selectList(String statement) {
  return this.sqlSessionProxy.<E> selectList(statement);
 }
 /**
  * {@inheritDoc}
  */
 public <E> List<E> selectList(String statement, Object parameter) {
  return this.sqlSessionProxy.<E> selectList(statement, parameter);
 }
 /**
  * {@inheritDoc}
  */
 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);
 }
 /**
  * {@inheritDoc}
  */
 public void select(String statement, ResultHandler handler) {
  this.sqlSessionProxy.select(statement, handler);
 }
 /**
  * {@inheritDoc}
  */
 public void select(String statement, Object parameter, ResultHandler handler) {
  this.sqlSessionProxy.select(statement, parameter, handler);
 }
 /**
  * {@inheritDoc}
  */
 public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
 }
 /**
  * {@inheritDoc}
  */
 public int insert(String statement) {
  return this.sqlSessionProxy.insert(statement);
 }
 /**
  * {@inheritDoc}
  */
 public int insert(String statement, Object parameter) {
  return this.sqlSessionProxy.insert(statement, parameter);
 }
 /**
  * {@inheritDoc}
  */
 public int update(String statement) {
  return this.sqlSessionProxy.update(statement);
 }
 /**
  * {@inheritDoc}
  */
 public int update(String statement, Object parameter) {
  return this.sqlSessionProxy.update(statement, parameter);
 }
 /**
  * {@inheritDoc}
  */
 public int delete(String statement) {
  return this.sqlSessionProxy.delete(statement);
 }
 /**
  * {@inheritDoc}
  */
 public int delete(String statement, Object parameter) {
  return this.sqlSessionProxy.delete(statement, parameter);
 }
 /**
  * {@inheritDoc}
  */
 public <T> T getMapper(Class<T> type) {
  return getConfiguration().getMapper(type, this);
 }
 /**
  * {@inheritDoc}
  */
 public void commit() {
  throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
 }
 /**
  * {@inheritDoc}
  */
 public void commit(boolean force) {
  throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
 }
 /**
  * {@inheritDoc}
  */
 public void rollback() {
  throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
 }
 /**
  * {@inheritDoc}
  */
 public void rollback(boolean force) {
  throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
 }
 /**
  * {@inheritDoc}
  */
 public void close() {
  throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
 }
 /**
  * {@inheritDoc}
  */
 public void clearCache() {
  this.sqlSessionProxy.clearCache();
 }
 /**
  * {@inheritDoc}
  */
 public Connection getConnection() {
  return this.sqlSessionProxy.getConnection();
 }
 /**
  * {@inheritDoc}
  * @since 1.0.2
  */
 public List<BatchResult> flushStatements() {
  return this.sqlSessionProxy.flushStatements();
 }
 /**
  * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
  * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to
  * the {@code PersistenceExceptionTranslator}.
  */
 private class SqlSessionInterceptor implements InvocationHandler {
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   final SqlSession sqlSession = getSqlSession(
     EjsSqlSessionTemplate.this.getSqlSessionFactory(),
     EjsSqlSessionTemplate.this.executorType,
     EjsSqlSessionTemplate.this.exceptionTranslator);
   try {
    Object result = method.invoke(sqlSession, args);
    if (!isSqlSessionTransactional(sqlSession, EjsSqlSessionTemplate.this.getSqlSessionFactory())) {
     // force commit even on non-dirty sessions because some databases require
     // a commit/rollback before calling close()
     sqlSession.commit(true);
    }
    return result;
   } catch (Throwable t) {
    Throwable unwrapped = unwrapThrowable(t);
    if (EjsSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
     Throwable translated = EjsSqlSessionTemplate.this.exceptionTranslator
       .translateExceptionIfPossible((PersistenceException) unwrapped);
     if (translated != null) {
      unwrapped = translated;
     }
    }
    throw unwrapped;
   } finally {
    closeSqlSession(sqlSession, EjsSqlSessionTemplate.this.getSqlSessionFactory());
   }
  }
 }
} 

2。自定义了一个注解

/**
 * 注解式数据源,用来进行数据源切换
 * User:Amos.zhou
 * Date: 14-2-27
 * Time: 下午2:34
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChooseDataSource {
 String value() default "";
} 

3.定义一个AspectJ的切面(我习惯用AspectJ,因为spring AOP不支持cflow()这些语法),所以在编译,打包的时候一定要用aspectJ的编译器,不能直接用原生的JDK。有些方法就是我基于以前Hibernate,JDBC动态数据源的时候改动的。

/**
 * <li>类描述:完成数据源的切换,抽类切面,具体项目继承一下,不需要重写即可使用</li>
 *
 * @author: amos.zhou
 * 2013-8-1 上午11:51:40
 * @since v1.0
 */
@Aspect
public abstract class ChooseDataSourceAspect {
 protected static final ThreadLocal<String> preDatasourceHolder = new ThreadLocal<String>();
 @Pointcut("execution(public * *.*(..))")
 public void allMethodPoint() {
 }
 @Pointcut("@within(com.ejushang.spider.annotation.ChooseDataSource) && allMethodPoint()")
 public void allServiceMethod() {
 }
 /**
  * 对所有注解有ChooseDataSource的类进行拦截
  */
 @Pointcut("cflow(allServiceMethod()) && allServiceMethod()")
 public void changeDatasourcePoint() {
 }
 /**
  * 根据@ChooseDataSource的属性值设置不同的dataSourceKey,以供DynamicDataSource
  */
 @Before("changeDatasourcePoint()")
 public void changeDataSourceBeforeMethodExecution(JoinPoint jp) {
  //拿到anotation中配置的数据源
  String resultDS = determineDatasource(jp);
  //没有配置实用默认数据源
  if (resultDS == null) {
   SqlSessionContextHolder.setSessionFactoryKey(null);
   return;
  }
  preDatasourceHolder.set(SqlSessionContextHolder.getDataSourceKey());
  //将数据源设置到数据源持有者
  SqlSessionContextHolder.setSessionFactoryKey(resultDS);
 }
 /**
  * <p>创建时间: 2013-8-20 上午9:48:44</p>
  * 如果需要修改获取数据源的逻辑,请重写此方法
  *
  * @param jp
  * @return
  */
 @SuppressWarnings("rawtypes")
 protected String determineDatasource(JoinPoint jp) {
  String methodName = jp.getSignature().getName();
  Class targetClass = jp.getSignature().getDeclaringType();
  String dataSourceForTargetClass = resolveDataSourceFromClass(targetClass);
  String dataSourceForTargetMethod = resolveDataSourceFromMethod(
    targetClass, methodName);
  String resultDS = determinateDataSource(dataSourceForTargetClass,
    dataSourceForTargetMethod);
  return resultDS;
 }
 /**
  * 方法执行完毕以后,数据源切换回之前的数据源。
  * 比如foo()方法里面调用bar(),但是bar()另外一个数据源,
  * bar()执行时,切换到自己数据源,执行完以后,要切换到foo()所需要的数据源,以供
  * foo()继续执行。
  * <p>创建时间: 2013-8-16 下午4:27:06</p>
  */
 @After("changeDatasourcePoint()")
 public void restoreDataSourceAfterMethodExecution() {
  SqlSessionContextHolder.setSessionFactoryKey(preDatasourceHolder.get());
  preDatasourceHolder.remove();
 }
 /**
  * <li>创建时间: 2013-6-17 下午5:34:13</li> <li>创建人:amos.zhou</li> <li>方法描述 :</li>
  *
  * @param targetClass
  * @param methodName
  * @return
  */
 @SuppressWarnings("rawtypes")
 private String resolveDataSourceFromMethod(Class targetClass,
            String methodName) {
  Method m = ReflectUtil.findUniqueMethod(targetClass, methodName);
  if (m != null) {
   ChooseDataSource choDs = m.getAnnotation(ChooseDataSource.class);
   return resolveDataSourcename(choDs);
  }
  return null;
 }
 /**
  * <li>创建时间: 2013-6-17 下午5:06:02</li>
  * <li>创建人:amos.zhou</li>
  * <li>方法描述 : 确定
  * 最终数据源,如果方法上设置有数据源,则以方法上的为准,如果方法上没有设置,则以类上的为准,如果类上没有设置,则使用默认数据源</li>
  *
  * @param classDS
  * @param methodDS
  * @return
  */
 private String determinateDataSource(String classDS, String methodDS) {
//  if (null == classDS && null == methodDS) {
//   return null;
//  }
  // 两者必有一个不为null,如果两者都为Null,也会返回Null
  return methodDS == null ? classDS : methodDS;
 }
 /**
  * <li>创建时间: 2013-6-17 下午4:33:03</li> <li>创建人:amos.zhou</li> <li>方法描述 : 类级别的 @ChooseDataSource
  * 的解析</li>
  *
  * @param targetClass
  * @return
  */
 @SuppressWarnings({"unchecked", "rawtypes"})
 private String resolveDataSourceFromClass(Class targetClass) {
  ChooseDataSource classAnnotation = (ChooseDataSource) targetClass
    .getAnnotation(ChooseDataSource.class);
  // 直接为整个类进行设置
  return null != classAnnotation ? resolveDataSourcename(classAnnotation)
    : null;
 }
 /**
  * <li>创建时间: 2013-6-17 下午4:31:42</li> <li>创建人:amos.zhou</li> <li>方法描述 :
  * 组装DataSource的名字</li>
  *
  * @param ds
  * @return
  */
 private String resolveDataSourcename(ChooseDataSource ds) {
  return ds == null ? null : ds.value();
 }
} 

那么以上3个类,就可以作为一个公共的组件打个包了。

那么项目中具体 怎么用列?

4.  在项目中定义一个具体的AspectJ切面

@Aspect
public class OrderFetchAspect extends ChooseDataSourceAspect {
}

如果你的根据你的需要重写方法,我这边是不需要重写的,所以空切面就行了。

5.配置spring,在上面的分析过程中已经贴出了,基本上就是每个数据库,一个dataSource,每个DataSource一个SqlSessionFactory。最后配一个SqlSessionTemplate,也就是我们自己重写的。再就是MapperScan了,大致如下(数据库连接信息已去除,包名为杜撰):

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
</bean>
<bean id="dataSourceTb" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
</bean>
<!-- 事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 <property name="dataSource" ref="dataSource" />
</bean>
<!-- 注解控制事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 <property name="dataSource" ref="dataSource"/>
 <property name="configLocation" value="classpath:mybatis.xml" />
 <property name="mapperLocations" value="classpath*:com/foo/bar/**/config/*mapper.xml" />
</bean>
<bean id="sqlSessionFactoryTb" class="org.mybatis.spring.SqlSessionFactoryBean">
 <property name="dataSource" ref="dataSourceTb"/>
 <property name="configLocation" value="classpath:mybatis.xml" />
 <property name="mapperLocations" value="classpath*:<span style="font-family: Arial, Helvetica, sans-serif;">com/foo/bar</span><span style="font-family: Arial, Helvetica, sans-serif;">/**/configtb/*mapper.xml" /></span>
</bean>
<bean id="sqlSessionTemplate" class="com.foo.bar.template.EjsSqlSessionTemplate">
 <constructor-arg ref="sqlSessionFactory" />
 <property name="targetSqlSessionFactory">
  <map>
   <entry value-ref="sqlSessionFactory" key="spider"/>
   <entry value-ref="sqlSessionFactoryTb" key="sysinfo"/>
  </map>
 </property>
</bean>
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
 <property name="basePackage" value="com.foo.bar.**.mapper*" />
 <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>
</bean> 

6.具体应用

@ChooseDataSource("spider")
public class ShopServiceTest extends ErpTest {
 private static final Logger log = LoggerFactory.getLogger(ShopServiceTest.class);
 @Autowired
 private IShopService shopService;
 @Autowired
 private IJdpTbTradeService jdpTbTradeService;
 @Test
 @Rollback(false)
 public void testFindAllShop(){
  List<Shop> shopList1 = shopService.findAllShop();
  for(Shop shop : shopList1){
   System.out.println(shop);
  }
  fromTestDB();
 }
 @ChooseDataSource("sysinfo")
 private void fromTestDB(){
  List<Shop> shopList = jdpTbTradeService.findAllShop();
  for(Shop shop : shopList){
   System.out.println(shop);
  }
 }
} 

测试发现 shopList1是从spider库查出来的数据,而fromDB则是从sysinfo中查出来的数据。 那么我们就大功告成。
要做到我以上功能,Spring AOP是做不到的,因为他不支持Cflow(),这也就是我为什么要用AspectJ的原因。

-----------------------------------------------------------------------------------------------再次分割线-------------------------------------------------------------------------------------------------------------------

好了,功能我们已经实现了,你有没有觉得很麻烦,这一点也不Spring的风格,Spring的各个地方扩展都是很方便的。那么我们看看,在SqlSessionTemplate中的什么地方改动一下,我们就可以很轻松的实现这个功能列?大家可以理解了,再回去看一下源码。

其实,只要将源码中的那个SqlSessionInterceptor的这句话:

final SqlSession sqlSession = getSqlSession(
   SqlSessionTemplate.this.sqlSessionFactory,
   SqlSessionTemplate.this.executorType,
   SqlSessionTemplate.this.exceptionTranslator); 

改为:

final SqlSession sqlSession = getSqlSession(
     EjsSqlSessionTemplate.this.getSqlSessionFactory(),
     EjsSqlSessionTemplate.this.executorType,
		 EjsSqlSessionTemplate.this.exceptionTranslator); 

保证 每次在产生Session代理的时候,传进去的参数都是调用getSqlSessionFactory()获取,那么我们自定义的SqlSessionTemplate,只要重写getSqlSessionFactory(),加多一个以下2句话:

private Map<Object, SqlSessionFactory> targetSqlSessionFactory;
 public void setTargetSqlSessionFactory(Map<Object, SqlSessionFactory> targetSqlSessionFactory) {
  this.targetSqlSessionFactory = targetSqlSessionFactory;
 } 

那么就完全可以实现动态数据源切换。  那么mybatis-spring的项目团队会这样维护么? 我会以mail的方式与他们沟通。至于能否改进,我们不得而知了。

其实这也就引发一个关于面向对象设计时的思想,也是一直争论得喋喋不休的一个问题:

在类的方法中,如果要用到类的属性时,是直接用this.filedName  来操作,还是用  getFiledName() 来进行操作?

其实以前我也是偏向于直接用this.属性来进行操作的,但是经历过这次以后,我想我会偏向于后者。

以上所述是小编给大家介绍的关于Spring3 + Mybatis3整合时多数据源动态切换的问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Spring3 整合MyBatis3 配置多数据源动态选择SqlSessionFactory详细教程

    一.摘要 这篇文章将介绍Spring整合Mybatis 如何完成SqlSessionFactory的动态切换的.并且会简单的介绍下MyBatis整合Spring中的官方的相关代码. Spring整合MyBatis切换SqlSessionFactory有两种方法 第一. 继承SqlSessionDaoSupport,重写获取SqlSessionFactory的方法. 第二.继承SqlSessionTemplate 重写getSqlSessionFactory.getConfiguration和Sq

  • 详解spring+springmvc+mybatis整合注解

    每天记录一点点,慢慢的成长,今天我们学习了ssm,这是我自己总结的笔记,大神勿喷!谢谢,主要代码!! ! spring&springmvc&mybatis整合(注解) 1.jar包 2.引入web.xml文件 <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param

  • Spring Boot整合MyBatis操作过程

    1.加入mybatis-spring-boot-stater的Maven依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> 2.配置数据源 在src/main/re

  • Spring Boot 整合 Mybatis Annotation 注解的完整 Web 案例

    前言 距离第一篇 Spring Boot 系列的博文 3 个月了.虽然 XML 形式是我比较推荐的,但是注解形式也是方便的.尤其一些小系统,快速的 CRUD 轻量级的系统. 这里感谢晓春 http://xchunzhao.tk/ 的 Pull Request,提供了 springboot-mybatis-annotation 的实现. 一.运行 springboot-mybatis-annotation 工程 然后Application 应用启动类的 main 函数,然后在浏览器访问: http

  • 详解Spring Boot整合Mybatis实现 Druid多数据源配置

    一.多数据源的应用场景 目前,业界流行的数据操作框架是 Mybatis,那 Druid 是什么呢? Druid 是 Java 的数据库连接池组件.Druid 能够提供强大的监控和扩展功能.比如可以监控 SQL ,在监控业务可以查询慢查询 SQL 列表等.Druid 核心主要包括三部分: 1. DruidDriver 代理 Driver,能够提供基于 Filter-Chain 模式的插件体系. 2. DruidDataSource 高效可管理的数据库连接池 3. SQLParser 当业务数据量达

  • spring与mybatis整合配置文件

    最近因为项目要求整合了spring+mybatis架构进行项目开发,现将相关整合配置文件整理如下: 基本架构:spring+springmvc+mybatis 分布式框架:dubbo+zookeeper 数据库:mysql 数据库连接池:Druid 1 数据库连接配置信息jdbc.properties #mysql version database druid # setting validationQuery=SELECT 1 jdbc.driverClassName=com.mysql.jd

  • 关于Spring3 + Mybatis3整合时多数据源动态切换的问题

    以前的项目经历中,基本上都是spring + hibernate + Spring JDBC这种组合用的多.至于MyBatis,也就这个项目才开始试用,闲话不多说,进入正题. 以前的这种框架组合中,动态数据源切换可谓已经非常成熟了,网上也有非常多的博客介绍,都是继承AbstractRoutingDataSource,重写determineCurrentLookupKey()方法.具体做法就不在此废话了. 所以当项目中碰到这个问题,我几乎想都没有想,就采用了这种做法,但是一测试,一点反应都没有.当

  • spring boot多数据源动态切换代码实例

    这篇文章主要介绍了spring boot多数据源动态切换代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 当项目中存在多数据源时,就涉及到数据源的动态切换,通过研究,特此记录一下. 1.maven依赖 <!--数据库连接--> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> &

  • Spring AOP实现多数据源动态切换

    目录 需求背景 分析及实现 配置多数据源信息 Spring如何获取配置好的多个数据源信息? Spring如何选择使用数据源? 结语 需求背景 去年底,公司项目有一个需求中有个接口需要用到平台.算法.大数据等三个不同数据库的数据进行计算.组装以及最后的展示,当时这个需求是另一个老同事在做,我只是负责自己的部分.直到今年回来了,这个项目也做得差不多了,这会儿才有时间区仔细看同事的代码,是怎么去实现多数据源动态切换的. 扩展:当业务也来越复杂,数据量越来越庞大时,就可能会对数据库进行分库分表.读写分离

  • mybatis多数据源动态切换的完整步骤

    笔者主要从事c#开发,近期因为项目需要,搭建了一套spring-cloud微服务框架,集成了eureka服务注册中心. gateway网关过滤.admin服务监控.auth授权体系验证,集成了redis.swagger.jwt.mybatis多数据源等各项功能. 具体搭建过程后续另写播客介绍.具体结构如下: 在搭建过程集成mybatis的时候,考虑到单一数据源无法满足实际业务需要,故结合c#的开发经验,进行多数据源动态集成. mybatis的多数据源可以采用两种方式进行,第一种是分包方式实现,这

  • springboot中mybatis多数据源动态切换实现

    目录 多数据源配置引入 动态数据源路由实现 动态数据源切换使用 案例源码 在开发中,动态数据源配置还是用的比较多的,比如在多数据源使用方面,又或者是在多个DB之间切换方面.这里给出一个动态数据源的配置方案,两个DB均以mysql为例. 多数据源配置引入 mybatis和mysql在springboot中的引入这里就不在说了,不了解的可以参见springboot中mysql与mybatis的引入. 数据源配置如下: datasource: master: type: com.alibaba.dru

  • SpringBoot基于AbstractRoutingDataSource实现多数据源动态切换

    目录 一.场景 二.原理 三.代码示例 一.场景 在生产业务中,有一些任务执行了耗时较长的查询操作,在实时性要求不高的时候,我们希望将这些查询sql分离出来,去从库查询,以减少应用对主数据库的压力. 一种方案是在配置文件中配置多个数据源,然后通过配置类来获取数据源以及mapper相关的扫描配置,不同的数据源配置不佟的mapper扫描位置,然后需要哪一个数据源就注入哪一个mapper接口即可,这种方法比较简单.特征是通过mapper扫描位置区分数据源. 第二种方案是配置一个默认使用的数据源,然后定

  • spring boot + mybatis实现动态切换数据源实例代码

    前言 前几天有个需求,需要使用不同的数据源,例如某业务要用A数据源,另一个业务要用B数据源.我上网收集了一些资料整合了一下,虽然最后这个需求不了了之了,但是多数据源动态切换还是蛮好用的,所以记录一下,或许以后有用呢?或者自己感兴趣又想玩呢! 下面话不多说了,随着小编来一起看看详细的介绍吧 方法如下: 1.加个依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybat

  • druid多数据源配置+Datasurce动态切换方式

    目录 druid多数据源配置+Datasurce动态切换 AbstractRoutingDataSource 数据源动态切换 例子 配置多数据源并实现Druid自动切换 配置yml文件 主数据源配置 从数据源配置 使用dao 日志 druid多数据源配置+Datasurce动态切换 AbstractRoutingDataSource 数据源动态切换 spring 使用AbstractRoutingDataSource自定义动态数据源时的事务处理, 需要继承spring的AbstractRouti

  • Spring整合多数据源实现动态切换的实例讲解

    在实际项目中时常需要连接多个数据库,而且不同的业务需求在实现过程当中往往需要访问不同的数据库. jdbc.properties配置文件,配置多个dataSource ##########################MySQL##################################### hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect connection.driver_class=com.mysql.jdbc.

随机推荐