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

http://blog.csdn.net/zl3450341/article/details/20150687

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

 

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

 

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

 

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

 

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

 

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

 

[java]  view plain  copy
 
  1. final SqlSession sqlSession = getSqlSession(  
  2.     SqlSessionTemplate.this.sqlSessionFactory,  
  3.     SqlSessionTemplate.this.executorType,  
  4.     SqlSessionTemplate.this.exceptionTranslator);  

 

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

 

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

 

[java]  view plain  copy
 
  1. public class EjsSqlSessionTemplate extends SqlSessionTemplate {  
  2.   
  3.     private Map<String, SqlSessionFactory> targetSqlSessionFactory = new HashMap<String, SqlSessionFactory>();  
  4.     public void setTargetSqlSessionFactory(Map<String, SqlSessionFactory> targetSqlSessionFactory) {  
  5.         this.targetSqlSessionFactory = targetSqlSessionFactory;  
  6.     }  


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

 

 

[html]  view plain  copy
 
  1. <bean id="sqlSessionTemplate" class="com.ejushang.spider.datasource.EjsSqlSessionTemplate">  
  2.        <constructor-arg ref="sqlSessionFactory" />  
  3.        <property name="targetSqlSessionFactory">  
  4.            <map>  
  5.                <entry value-ref="sqlSessionFactory" key="spider"/>  
  6.                <entry value-ref="sqlSessionFactoryTb" key="sysinfo"/>  
  7.            </map>  
  8.        </property>  
  9.    </bean>  
  10.   
  11.    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
  12.        <property name="basePackage" value="com.foo.bar.**.mapper*" />  
  13.        <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>  
  14.    </bean>  



 

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

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

 

[java]  view plain  copy
 
  1. /** 
  2.      * 重写得到SqlSessionFactory的方法 
  3.      * @return 
  4.      */  
  5.     @Override  
  6.     public SqlSessionFactory getSqlSessionFactory() {  
  7.   
  8.         SqlSessionFactory targetSqlSessionFactory = this.targetSqlSessionFactory.get(SqlSessionContextHolder.getDataSourceKey());  
  9.         if (targetSqlSessionFactory != null) {  
  10.             return targetSqlSessionFactory;  
  11.         } else if ( this.getSqlSessionFactory() != null) {  
  12.             return  this.getSqlSessionFactory();  
  13.         }  
  14.         throw new IllegalArgumentException("sqlSessionFactory or targetSqlSessionFactory must set one at least");  
  15.     }  

 

 

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

 

[java]  view plain  copy
 
  1. public class SqlSessionContextHolder {  
  2.     private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();  
  3.     private static Logger logger = LoggerFactory.getLogger(SqlSessionContextHolder.class);  
  4.   
  5.     public static void setSessionFactoryKey(String dataSourceKey) {  
  6.         contextHolder.set(dataSourceKey);  
  7.     }  
  8.   
  9.     public static String getDataSourceKey() {  
  10.         String key = contextHolder.get();  
  11.         logger.info("当前线程Thread:"+Thread.currentThread().getName()+" 当前的数据源 key is "+ key);  
  12.         return key;  
  13.     }  
  14.   
  15. }  


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

 

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

 

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

 

[java]  view plain  copy
 
  1. final SqlSession sqlSession = getSqlSession(  
  2.                    EjsSqlSessionTemplate.this.getSqlSessionFactory(),  
  3.                    EjsSqlSessionTemplate.this.getExecutorType(),  
  4.                    EjsSqlSessionTemplate.this.getPersistenceExceptionTranslator());  


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

 

 

[java]  view plain  copy
 
  1. public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,  
  2.     PersistenceExceptionTranslator exceptionTranslator) {  
  3.   
  4.   notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");  
  5.   notNull(executorType, "Property 'executorType' is required");  
  6.   
  7.   this.sqlSessionFactory = sqlSessionFactory;  
  8.   this.executorType = executorType;  
  9.   this.exceptionTranslator = exceptionTranslator;  
  10.   this.sqlSessionProxy = (SqlSession) newProxyInstance(  
  11.       SqlSessionFactory.class.getClassLoader(),  
  12.       new Class[] { SqlSession.class },  
  13.       new SqlSessionInterceptor());  
  14. }  


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

 

 

[java]  view plain  copy
 
  1. public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {  
  2.       super(getSqlSessionFactory(), executorType, exceptionTranslator);  
  3.   }  


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

 

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

 

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

 

[java]  view plain  copy
 
  1. public class EjsSqlSessionTemplate extends SqlSessionTemplate {  
  2.   
  3.     private final SqlSessionFactory sqlSessionFactory;  
  4.     private final ExecutorType executorType;  
  5.     private final SqlSession sqlSessionProxy;  
  6.     private final PersistenceExceptionTranslator exceptionTranslator;  
  7.   
  8.     private Map<Object, SqlSessionFactory> targetSqlSessionFactory;  
  9.   
  10.     public void setTargetSqlSessionFactory(Map<Object, SqlSessionFactory> targetSqlSessionFactory) {  
  11.         this.targetSqlSessionFactory = targetSqlSessionFactory;  
  12.     }  
  13.   
  14.   
  15.     public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {  
  16.         this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());  
  17.     }  
  18.   
  19.     public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {  
  20.         this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration()  
  21.                 .getEnvironment().getDataSource(), true));  
  22.     }  
  23.   
  24.     public EjsSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,  
  25.                                     PersistenceExceptionTranslator exceptionTranslator) {  
  26.   
  27.         super(sqlSessionFactory, executorType, exceptionTranslator);  
  28.   
  29.         this.sqlSessionFactory = sqlSessionFactory;  
  30.         this.executorType = executorType;  
  31.         this.exceptionTranslator = exceptionTranslator;  
  32.   
  33.         this.sqlSessionProxy = (SqlSession) newProxyInstance(  
  34.                 SqlSessionFactory.class.getClassLoader(),  
  35.                 new Class[] { SqlSession.class },  
  36.                 new SqlSessionInterceptor());  
  37.   
  38.     }  
  39.   
  40.   
  41.   
  42.     @Override  
  43.     public SqlSessionFactory getSqlSessionFactory() {  
  44.   
  45.         SqlSessionFactory targetSqlSessionFactory = this.targetSqlSessionFactory.get(SqlSessionContextHolder.getDataSourceKey());  
  46.         if (targetSqlSessionFactory != null) {  
  47.             return targetSqlSessionFactory;  
  48.         } else if ( this.sqlSessionFactory != null) {  
  49.             return  this.sqlSessionFactory;  
  50.         }  
  51.        throw new IllegalArgumentException("sqlSessionFactory or targetSqlSessionFactory must set one at least");  
  52.     }  
  53.   
  54.     @Override  
  55.     public Configuration getConfiguration() {  
  56.         return this.getSqlSessionFactory().getConfiguration();  
  57.     }  
  58.   
  59.     public ExecutorType getExecutorType() {  
  60.         return this.executorType;  
  61.     }  
  62.   
  63.     public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {  
  64.         return this.exceptionTranslator;  
  65.     }  
  66.   
  67.     /** 
  68.      * {@inheritDoc} 
  69.      */  
  70.     public <T> T selectOne(String statement) {  
  71.         return this.sqlSessionProxy.<T> selectOne(statement);  
  72.     }  
  73.   
  74.     /** 
  75.      * {@inheritDoc} 
  76.      */  
  77.     public <T> T selectOne(String statement, Object parameter) {  
  78.         return this.sqlSessionProxy.<T> selectOne(statement, parameter);  
  79.     }  
  80.   
  81.     /** 
  82.      * {@inheritDoc} 
  83.      */  
  84.     public <K, V> Map<K, V> selectMap(String statement, String mapKey) {  
  85.         return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey);  
  86.     }  
  87.   
  88.     /** 
  89.      * {@inheritDoc} 
  90.      */  
  91.     public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {  
  92.         return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey);  
  93.     }  
  94.   
  95.     /** 
  96.      * {@inheritDoc} 
  97.      */  
  98.     public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {  
  99.         return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey, rowBounds);  
  100.     }  
  101.   
  102.     /** 
  103.      * {@inheritDoc} 
  104.      */  
  105.     public <E> List<E> selectList(String statement) {  
  106.         return this.sqlSessionProxy.<E> selectList(statement);  
  107.     }  
  108.   
  109.     /** 
  110.      * {@inheritDoc} 
  111.      */  
  112.     public <E> List<E> selectList(String statement, Object parameter) {  
  113.         return this.sqlSessionProxy.<E> selectList(statement, parameter);  
  114.     }  
  115.   
  116.     /** 
  117.      * {@inheritDoc} 
  118.      */  
  119.     public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {  
  120.         return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);  
  121.     }  
  122.   
  123.     /** 
  124.      * {@inheritDoc} 
  125.      */  
  126.     public void select(String statement, ResultHandler handler) {  
  127.         this.sqlSessionProxy.select(statement, handler);  
  128.     }  
  129.   
  130.     /** 
  131.      * {@inheritDoc} 
  132.      */  
  133.     public void select(String statement, Object parameter, ResultHandler handler) {  
  134.         this.sqlSessionProxy.select(statement, parameter, handler);  
  135.     }  
  136.   
  137.     /** 
  138.      * {@inheritDoc} 
  139.      */  
  140.     public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {  
  141.         this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);  
  142.     }  
  143.   
  144.     /** 
  145.      * {@inheritDoc} 
  146.      */  
  147.     public int insert(String statement) {  
  148.         return this.sqlSessionProxy.insert(statement);  
  149.     }  
  150.   
  151.     /** 
  152.      * {@inheritDoc} 
  153.      */  
  154.     public int insert(String statement, Object parameter) {  
  155.         return this.sqlSessionProxy.insert(statement, parameter);  
  156.     }  
  157.   
  158.     /** 
  159.      * {@inheritDoc} 
  160.      */  
  161.     public int update(String statement) {  
  162.         return this.sqlSessionProxy.update(statement);  
  163.     }  
  164.   
  165.     /** 
  166.      * {@inheritDoc} 
  167.      */  
  168.     public int update(String statement, Object parameter) {  
  169.         return this.sqlSessionProxy.update(statement, parameter);  
  170.     }  
  171.   
  172.     /** 
  173.      * {@inheritDoc} 
  174.      */  
  175.     public int delete(String statement) {  
  176.         return this.sqlSessionProxy.delete(statement);  
  177.     }  
  178.   
  179.     /** 
  180.      * {@inheritDoc} 
  181.      */  
  182.     public int delete(String statement, Object parameter) {  
  183.         return this.sqlSessionProxy.delete(statement, parameter);  
  184.     }  
  185.   
  186.     /** 
  187.      * {@inheritDoc} 
  188.      */  
  189.     public <T> T getMapper(Class<T> type) {  
  190.         return getConfiguration().getMapper(type, this);  
  191.     }  
  192.   
  193.     /** 
  194.      * {@inheritDoc} 
  195.      */  
  196.     public void commit() {  
  197.         throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");  
  198.     }  
  199.   
  200.     /** 
  201.      * {@inheritDoc} 
  202.      */  
  203.     public void commit(boolean force) {  
  204.         throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");  
  205.     }  
  206.   
  207.     /** 
  208.      * {@inheritDoc} 
  209.      */  
  210.     public void rollback() {  
  211.         throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");  
  212.     }  
  213.   
  214.     /** 
  215.      * {@inheritDoc} 
  216.      */  
  217.     public void rollback(boolean force) {  
  218.         throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");  
  219.     }  
  220.   
  221.     /** 
  222.      * {@inheritDoc} 
  223.      */  
  224.     public void close() {  
  225.         throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");  
  226.     }  
  227.   
  228.     /** 
  229.      * {@inheritDoc} 
  230.      */  
  231.     public void clearCache() {  
  232.         this.sqlSessionProxy.clearCache();  
  233.     }  
  234.   
  235.     /** 
  236.      * {@inheritDoc} 
  237.      */  
  238.     public Connection getConnection() {  
  239.         return this.sqlSessionProxy.getConnection();  
  240.     }  
  241.   
  242.     /** 
  243.      * {@inheritDoc} 
  244.      * @since 1.0.2 
  245.      */  
  246.     public List<BatchResult> flushStatements() {  
  247.         return this.sqlSessionProxy.flushStatements();  
  248.     }  
  249.   
  250.     /** 
  251.      * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also 
  252.      * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to 
  253.      * the {@code PersistenceExceptionTranslator}. 
  254.      */  
  255.     private class SqlSessionInterceptor implements InvocationHandler {  
  256.         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  257.             final SqlSession sqlSession = getSqlSession(  
  258.                     EjsSqlSessionTemplate.this.getSqlSessionFactory(),  
  259.                     EjsSqlSessionTemplate.this.executorType,  
  260.                     EjsSqlSessionTemplate.this.exceptionTranslator);  
  261.             try {  
  262.                 Object result = method.invoke(sqlSession, args);  
  263.                 if (!isSqlSessionTransactional(sqlSession, EjsSqlSessionTemplate.this.getSqlSessionFactory())) {  
  264.                     // force commit even on non-dirty sessions because some databases require  
  265.                     // a commit/rollback before calling close()  
  266.                     sqlSession.commit(true);  
  267.                 }  
  268.                 return result;  
  269.             } catch (Throwable t) {  
  270.                 Throwable unwrapped = unwrapThrowable(t);  
  271.                 if (EjsSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {  
  272.                     Throwable translated = EjsSqlSessionTemplate.this.exceptionTranslator  
  273.                             .translateExceptionIfPossible((PersistenceException) unwrapped);  
  274.                     if (translated != null) {  
  275.                         unwrapped = translated;  
  276.                     }  
  277.                 }  
  278.                 throw unwrapped;  
  279.             } finally {  
  280.                 closeSqlSession(sqlSession, EjsSqlSessionTemplate.this.getSqlSessionFactory());  
  281.             }  
  282.         }  
  283.     }  
  284. }  



 

2。自定义了一个注解

 

[java]  view plain  copy
 
  1. /** 
  2.  * 注解式数据源,用来进行数据源切换 
  3.  * User:Amos.zhou 
  4.  * Date: 14-2-27 
  5.  * Time: 下午2:34 
  6.  */  
  7. @Target({ElementType.METHOD, ElementType.TYPE})  
  8. @Retention(RetentionPolicy.RUNTIME)  
  9. @Documented  
  10. public @interface ChooseDataSource {  
  11.   
  12.     String value() default "";  
  13. }  

 

 

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

[java]  view plain  copy
 
  1. /** 
  2.  * <li>类描述:完成数据源的切换,抽类切面,具体项目继承一下,不需要重写即可使用</li> 
  3.  * 
  4.  * @author: amos.zhou 
  5.  * 2013-8-1 上午11:51:40 
  6.  * @since v1.0 
  7.  */  
  8. @Aspect  
  9. public abstract class ChooseDataSourceAspect {  
  10.   
  11.     protected static final ThreadLocal<String> preDatasourceHolder = new ThreadLocal<String>();  
  12.   
  13.     @Pointcut("execution(public * *.*(..))")  
  14.     public void allMethodPoint() {  
  15.   
  16.     }  
  17.   
  18.     @Pointcut("@within(com.ejushang.spider.annotation.ChooseDataSource) && allMethodPoint()")  
  19.     public void allServiceMethod() {  
  20.   
  21.     }  
  22.   
  23.   
  24.     /** 
  25.      * 对所有注解有ChooseDataSource的类进行拦截 
  26.      */  
  27.     @Pointcut("cflow(allServiceMethod()) && allServiceMethod()")  
  28.     public void changeDatasourcePoint() {  
  29.     }  
  30.   
  31.   
  32.     /** 
  33.      * 根据@ChooseDataSource的属性值设置不同的dataSourceKey,以供DynamicDataSource 
  34.      */  
  35.     @Before("changeDatasourcePoint()")  
  36.     public void changeDataSourceBeforeMethodExecution(JoinPoint jp) {  
  37.         //拿到anotation中配置的数据源  
  38.         String resultDS = determineDatasource(jp);  
  39.         //没有配置实用默认数据源  
  40.         if (resultDS == null) {  
  41.             SqlSessionContextHolder.setSessionFactoryKey(null);  
  42.             return;  
  43.         }  
  44.         preDatasourceHolder.set(SqlSessionContextHolder.getDataSourceKey());  
  45.         //将数据源设置到数据源持有者  
  46.         SqlSessionContextHolder.setSessionFactoryKey(resultDS);  
  47.   
  48.     }  
  49.   
  50.     /** 
  51.      * <p>创建时间: 2013-8-20 上午9:48:44</p> 
  52.      * 如果需要修改获取数据源的逻辑,请重写此方法 
  53.      * 
  54.      * @param jp 
  55.      * @return 
  56.      */  
  57.     @SuppressWarnings("rawtypes")  
  58.     protected String determineDatasource(JoinPoint jp) {  
  59.         String methodName = jp.getSignature().getName();  
  60.         Class targetClass = jp.getSignature().getDeclaringType();  
  61.         String dataSourceForTargetClass = resolveDataSourceFromClass(targetClass);  
  62.         String dataSourceForTargetMethod = resolveDataSourceFromMethod(  
  63.                 targetClass, methodName);  
  64.         String resultDS = determinateDataSource(dataSourceForTargetClass,  
  65.                 dataSourceForTargetMethod);  
  66.         return resultDS;  
  67.     }  
  68.   
  69.   
  70.     /** 
  71.      * 方法执行完毕以后,数据源切换回之前的数据源。 
  72.      * 比如foo()方法里面调用bar(),但是bar()另外一个数据源, 
  73.      * bar()执行时,切换到自己数据源,执行完以后,要切换到foo()所需要的数据源,以供 
  74.      * foo()继续执行。 
  75.      * <p>创建时间: 2013-8-16 下午4:27:06</p> 
  76.      */  
  77.     @After("changeDatasourcePoint()")  
  78.     public void restoreDataSourceAfterMethodExecution() {  
  79.         SqlSessionContextHolder.setSessionFactoryKey(preDatasourceHolder.get());  
  80.         preDatasourceHolder.remove();  
  81.     }  
  82.   
  83.   
  84.     /** 
  85.      * <li>创建时间: 2013-6-17 下午5:34:13</li> <li>创建人:amos.zhou</li> <li>方法描述 :</li> 
  86.      * 
  87.      * @param targetClass 
  88.      * @param methodName 
  89.      * @return 
  90.      */  
  91.     @SuppressWarnings("rawtypes")  
  92.     private String resolveDataSourceFromMethod(Class targetClass,  
  93.                                                String methodName) {  
  94.         Method m = ReflectUtil.findUniqueMethod(targetClass, methodName);  
  95.         if (m != null) {  
  96.             ChooseDataSource choDs = m.getAnnotation(ChooseDataSource.class);  
  97.             return resolveDataSourcename(choDs);  
  98.         }  
  99.         return null;  
  100.     }  
  101.   
  102.     /** 
  103.      * <li>创建时间: 2013-6-17 下午5:06:02</li> 
  104.      * <li>创建人:amos.zhou</li> 
  105.      * <li>方法描述 : 确定 
  106.      * 最终数据源,如果方法上设置有数据源,则以方法上的为准,如果方法上没有设置,则以类上的为准,如果类上没有设置,则使用默认数据源</li> 
  107.      * 
  108.      * @param classDS 
  109.      * @param methodDS 
  110.      * @return 
  111.      */  
  112.     private String determinateDataSource(String classDS, String methodDS) {  
  113. //        if (null == classDS && null == methodDS) {  
  114. //            return null;  
  115. //        }  
  116.         // 两者必有一个不为null,如果两者都为Null,也会返回Null  
  117.         return methodDS == null ? classDS : methodDS;  
  118.     }  
  119.   
  120.     /** 
  121.      * <li>创建时间: 2013-6-17 下午4:33:03</li> <li>创建人:amos.zhou</li> <li>方法描述 : 类级别的 @ChooseDataSource 
  122.      * 的解析</li> 
  123.      * 
  124.      * @param targetClass 
  125.      * @return 
  126.      */  
  127.     @SuppressWarnings({"unchecked""rawtypes"})  
  128.     private String resolveDataSourceFromClass(Class targetClass) {  
  129.         ChooseDataSource classAnnotation = (ChooseDataSource) targetClass  
  130.                 .getAnnotation(ChooseDataSource.class);  
  131.         // 直接为整个类进行设置  
  132.         return null != classAnnotation ? resolveDataSourcename(classAnnotation)  
  133.                 : null;  
  134.     }  
  135.   
  136.     /** 
  137.      * <li>创建时间: 2013-6-17 下午4:31:42</li> <li>创建人:amos.zhou</li> <li>方法描述 : 
  138.      * 组装DataSource的名字</li> 
  139.      * 
  140.      * @param ds 
  141.      * @return 
  142.      */  
  143.     private String resolveDataSourcename(ChooseDataSource ds) {  
  144.         return ds == null ? null : ds.value();  
  145.     }  
  146.   
  147. }  

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

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

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

[java]  view plain  copy
 
  1. @Aspect  
  2. public class OrderFetchAspect extends ChooseDataSourceAspect {  
  3. }  

 

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

 

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

 

[html]  view plain  copy
 
  1. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">  
  2.       
  3. </bean>  
  4.   
  5. <bean id="dataSourceTb" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">  
  6.       
  7. </bean>  
  8.   
  9. <!-- 事务管理 -->  
  10. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  11.     <property name="dataSource" ref="dataSource" />  
  12. </bean>  
  13. <!-- 注解控制事务 -->  
  14. <tx:annotation-driven transaction-manager="transactionManager"/>  
  15.   
  16.   
  17. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
  18.     <property name="dataSource" ref="dataSource"/>  
  19.     <property name="configLocation" value="classpath:mybatis.xml" />  
  20.     <property name="mapperLocations" value="classpath*:com/foo/bar/**/config/*mapper.xml" />  
  21. </bean>  
  22.   
  23. <bean id="sqlSessionFactoryTb" class="org.mybatis.spring.SqlSessionFactoryBean">  
  24.     <property name="dataSource" ref="dataSourceTb"/>  
  25.     <property name="configLocation" value="classpath:mybatis.xml" />  
  26.     <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>  
  27. </bean>  
  28.   
  29. <bean id="sqlSessionTemplate" class="com.foo.bar.template.EjsSqlSessionTemplate">  
  30.     <constructor-arg ref="sqlSessionFactory" />  
  31.     <property name="targetSqlSessionFactory">  
  32.         <map>  
  33.             <entry value-ref="sqlSessionFactory" key="spider"/>  
  34.             <entry value-ref="sqlSessionFactoryTb" key="sysinfo"/>  
  35.         </map>  
  36.     </property>  
  37. </bean>  
  38.   
  39. <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
  40.     <property name="basePackage" value="com.foo.bar.**.mapper*" />  
  41.     <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>  
  42. </bean>  



 

6.具体应用

 

[java]  view plain  copy
 
  1. @ChooseDataSource("spider")  
  2. public class ShopServiceTest extends ErpTest {  
  3.   
  4.     private static final Logger log = LoggerFactory.getLogger(ShopServiceTest.class);  
  5.   
  6.     @Autowired  
  7.     private IShopService shopService;  
  8.   
  9.     @Autowired  
  10.     private IJdpTbTradeService jdpTbTradeService;  
  11.   
  12.   
  13.     @Test  
  14.     @Rollback(false)  
  15.     public void testFindAllShop(){  
  16.         List<Shop> shopList1 = shopService.findAllShop();  
  17.         for(Shop shop : shopList1){  
  18.             System.out.println(shop);  
  19.         }  
  20.   
  21.         fromTestDB();  
  22.     }  
  23.   
  24.     @ChooseDataSource("sysinfo")  
  25.     private void fromTestDB(){  
  26.         List<Shop> shopList = jdpTbTradeService.findAllShop();  
  27.         for(Shop shop : shopList){  
  28.             System.out.println(shop);  
  29.         }  
  30.     }  
  31. }  


测试发现 shopList1是从spider库查出来的数据,而fromDB则是从sysinfo中查出来的数据。 那么我们就大功告成。 

 

要做到我以上功能,Spring AOP是做不到的,因为他不支持Cflow(),这也就是我为什么要用AspectJ的原因。

 

 

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

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

 

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

 

[java]  view plain  copy
 
  1. final SqlSession sqlSession = getSqlSession(  
  2.          SqlSessionTemplate.this.sqlSessionFactory,  
  3.          SqlSessionTemplate.this.executorType,  
  4.          SqlSessionTemplate.this.exceptionTranslator);  


改为:

[java]  view plain  copy
 
  1. final SqlSession sqlSession = getSqlSession(  
  2.                    EjsSqlSessionTemplate.this.getSqlSessionFactory(),  
  3.                    EjsSqlSessionTemplate.this.executorType,  
  4.                    EjsSqlSessionTemplate.this.exceptionTranslator);  

 

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

 

[java]  view plain  copy
 
  1. private Map<Object, SqlSessionFactory> targetSqlSessionFactory;  
  2.   
  3.    public void setTargetSqlSessionFactory(Map<Object, SqlSessionFactory> targetSqlSessionFactory) {  
  4.        this.targetSqlSessionFactory = targetSqlSessionFactory;  
  5.    }  

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

 

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

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

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

猜你喜欢

转载自angie.iteye.com/blog/2398854
今日推荐