以前的项目经历中,基本上都是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.属性来进行操作的,但是经历过这次以后,我想我会偏向于后者。