MyException - 我的异常网
当前位置:我的异常网» 开源软件 » 关于Spring3 + Mybatis3整合时,多数据源动态切换的

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

www.MyException.Cn  网友分享于:2013-11-19  浏览:0次
关于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.属性来进行操作的,但是经历过这次以后,我想我会偏向于后者。

文章评论

程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
那些争议最大的编程观点
那些争议最大的编程观点
漫画:程序员的工作
漫画:程序员的工作
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
如何成为一名黑客
如何成为一名黑客
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
程序员应该关注的一些事儿
程序员应该关注的一些事儿
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
老程序员的下场
老程序员的下场
为什么程序员都是夜猫子
为什么程序员都是夜猫子
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
每天工作4小时的程序员
每天工作4小时的程序员
总结2014中国互联网十大段子
总结2014中国互联网十大段子
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
编程语言是女人
编程语言是女人
程序员都该阅读的书
程序员都该阅读的书
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
10个调试和排错的小建议
10个调试和排错的小建议
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
一个程序员的时间管理
一个程序员的时间管理
旅行,写作,编程
旅行,写作,编程
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
我的丈夫是个程序员
我的丈夫是个程序员
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
Java程序员必看电影
Java程序员必看电影
程序员和编码员之间的区别
程序员和编码员之间的区别
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
 程序员的样子
程序员的样子
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
代码女神横空出世
代码女神横空出世
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有