服务器之家

服务器之家 > 正文

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

时间:2020-09-16 15:17     来源/作者:兔子党-大胡子

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

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

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

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

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

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

?
1
2
3
4
final SqlSession sqlSession = getSqlSession(
 SqlSessionTemplate.this.sqlSessionFactory,
 SqlSessionTemplate.this.executorType,
 SqlSessionTemplate.this.exceptionTranslator);

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

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

?
1
2
3
4
5
6
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的配置文件就变成了这样:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
<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动态数据源的配置,看看是不是差不多?

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
  * 重写得到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的思想

?
1
2
3
4
5
6
7
8
9
10
11
12
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()方法。代码如下:

?
1
2
3
4
final SqlSession sqlSession = getSqlSession(   
EjsSqlSessionTemplate.this.getSqlSessionFactory(),   
EjsSqlSessionTemplate.this.getExecutorType(),    
EjsSqlSessionTemplate.this.getPersistenceExceptionTranslator());

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

?
1
2
3
4
5
6
7
8
9
10
11
12
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()。所以问题就出在这,那好吧,博主又重写构函数

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

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
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。自定义了一个注解

?
1
2
3
4
5
6
7
8
9
10
11
12
/**
 * 注解式数据源,用来进行数据源切换
 * 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动态数据源的时候改动的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
 * <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切面

?
1
2
3
@Aspect
public class OrderFetchAspect extends ChooseDataSourceAspect {
}

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<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.具体应用

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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的这句话:

?
1
2
3
4
final SqlSession sqlSession = getSqlSession(
   SqlSessionTemplate.this.sqlSessionFactory,
   SqlSessionTemplate.this.executorType,
   SqlSessionTemplate.this.exceptionTranslator);

改为:

?
1
2
3
4
final SqlSession sqlSession = getSqlSession(
     EjsSqlSessionTemplate.this.getSqlSessionFactory(),
     EjsSqlSessionTemplate.this.executorType,    
         EjsSqlSessionTemplate.this.exceptionTranslator);

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

?
1
2
3
4
private Map<Object, SqlSessionFactory> targetSqlSessionFactory;
 public void setTargetSqlSessionFactory(Map<Object, SqlSessionFactory> targetSqlSessionFactory) {
  this.targetSqlSessionFactory = targetSqlSessionFactory;
 }

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

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

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

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

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

原文链接:http://blog.csdn.net/zl3450341/article/details/20150687

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
Intellij idea2020永久破解,亲测可用!!!
Intellij idea2020永久破解,亲测可用!!! 2020-07-29
歪歪漫画vip账号共享2020_yy漫画免费账号密码共享
歪歪漫画vip账号共享2020_yy漫画免费账号密码共享 2020-04-07
电视剧《琉璃》全集在线观看 琉璃美人煞1-59集免费观看地址
电视剧《琉璃》全集在线观看 琉璃美人煞1-59集免费观看地址 2020-08-12
最新idea2020注册码永久激活(激活到2100年)
最新idea2020注册码永久激活(激活到2100年) 2020-07-29
返回顶部