一、摘要
这篇文章将介绍Spring整合Mybatis 如何完成SqlSessionFactory的动态切换的。并且会简单的介绍下MyBatis整合Spring中的官方的相关代码。
Spring整合MyBatis切换SqlSessionFactory有两种方法
第一、 继承SqlSessionDaoSupport,重写获取SqlSessionFactory的方法。
第二、继承SqlSessionTemplate 重写getSqlSessionFactory、getConfiguration和SqlSessionInterceptor这个拦截器。其中最为关键还是继承SqlSessionTemplate 并重写里面的方法。
而Spring整合MyBatis也有两种方式,一种是配置MapperFactoryBean,另一种则是利用MapperScannerConfigurer进行扫描接口或包完成对象的自动创建。相对来说后者更方便些。
MapperFactoryBean继承了SqlSessionDaoSupport也就是动态切换SqlSessionFactory的第一种方法,我们需要重写和实现SqlSessionDaoSupport方法,或者是继承MapperFactoryBean来重写覆盖相关方法。如果利用MapperScannerConfigurer的配置整合来切换SqlSessionFactory,那么我们就需要继承SqlSessionTemplate,重写上面提到的方法。在整合的配置中很多地方都是可以注入SqlSessionTemplate代替SqlSessionFactory的注入的。因为SqlSessionTemplate的创建也是需要注入SqlSessionFactory的。
二、实现代码
1、继承SqlSessionTemplate 重写getSqlSessionFactory、getConfiguration和SqlSessionInterceptor
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
|
package com.hoo.framework.mybatis.support; import static java.lang.reflect.Proxy.newProxyInstance; import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable; import static org.mybatis.spring.SqlSessionUtils.closeSqlSession; import static org.mybatis.spring.SqlSessionUtils.getSqlSession; import static org.mybatis.spring.SqlSessionUtils.isSqlSessionTransactional; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.sql.Connection; import java.util.List; import java.util.Map; import org.apache.ibatis.exceptions.PersistenceException; import org.apache.ibatis.executor.BatchResult; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.MyBatisExceptionTranslator; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.util.Assert; /** * <b>function:</b> 继承SqlSessionTemplate 重写相关方法 * @author hoojo * @createDate 2013-10-18 下午03:07:46 * @file CustomSqlSessionTemplate.java * @package com.hoo.framework.mybatis.support * @project SHMB * @blog http://blog.csdn.net/IBM_hoojo * @email hoojo_@126.com * @version 1.0 */ public class CustomSqlSessionTemplate extends SqlSessionTemplate { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator; private Map<Object, SqlSessionFactory> targetSqlSessionFactorys; private SqlSessionFactory defaultTargetSqlSessionFactory; public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) { this .targetSqlSessionFactorys = targetSqlSessionFactorys; } public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) { this .defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory; } public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { this (sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType()); } public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) { this (sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration() .getEnvironment().getDataSource(), true )); } public CustomSqlSessionTemplate(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()); this .defaultTargetSqlSessionFactory = sqlSessionFactory; } @Override public SqlSessionFactory getSqlSessionFactory() { SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(CustomerContextHolder.getContextType()); if (targetSqlSessionFactory != null ) { return targetSqlSessionFactory; } else if (defaultTargetSqlSessionFactory != null ) { return defaultTargetSqlSessionFactory; } else { Assert.notNull(targetSqlSessionFactorys, "Property 'targetSqlSessionFactorys' or 'defaultTargetSqlSessionFactory' are required" ); Assert.notNull(defaultTargetSqlSessionFactory, "Property 'defaultTargetSqlSessionFactory' or 'targetSqlSessionFactorys' are required" ); } return this .sqlSessionFactory; } @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( CustomSqlSessionTemplate. this .getSqlSessionFactory(), CustomSqlSessionTemplate. this .executorType, CustomSqlSessionTemplate. this .exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, CustomSqlSessionTemplate. 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 (CustomSqlSessionTemplate. this .exceptionTranslator != null && unwrapped instanceof PersistenceException) { Throwable translated = CustomSqlSessionTemplate. this .exceptionTranslator .translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null ) { unwrapped = translated; } } throw unwrapped; } finally { closeSqlSession(sqlSession, CustomSqlSessionTemplate. this .getSqlSessionFactory()); } } } } |
重写后的getSqlSessionFactory方法会从我们配置的SqlSessionFactory集合targetSqlSessionFactorys或默认的defaultTargetSqlSessionFactory中获取Session对象。而改写的SqlSessionInterceptor 是这个MyBatis整合Spring的关键,所有的SqlSessionFactory对象的session都将在这里完成创建、提交、关闭等操作。所以我们改写这里的代码,在这里获取getSqlSessionFactory的时候,从多个SqlSessionFactory中获取我们设置的那个即可。
上面添加了targetSqlSessionFactorys、defaultTargetSqlSessionFactory两个属性来配置多个SqlSessionFactory对象和默认的SqlSessionFactory对象。
CustomerContextHolder 设置SqlSessionFactory的类型
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
|
package com.hoo.framework.mybatis.support; /** * <b>function:</b> 多数据源 * @author hoojo * @createDate 2013-9-27 上午11:36:57 * @file CustomerContextHolder.java * @package com.hoo.framework.spring.support * @project SHMB * @blog http://blog.csdn.net/IBM_hoojo * @email hoojo_@126.com * @version 1.0 */ public abstract class CustomerContextHolder { public final static String SESSION_FACTORY_MYSQL = "mysql" ; public final static String SESSION_FACTORY_ORACLE = "oracle" ; private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static void setContextType(String contextType) { contextHolder.set(contextType); } public static String getContextType() { return contextHolder.get(); } public static void clearContextType() { contextHolder.remove(); } } |
2、配置相关的文件applicationContext-session-factory.xml
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
|
<?xml version= "1.0" encoding= "UTF-8" ?> <beans xmlns= "http://www.springframework.org/schema/beans" xmlns:aop= "http://www.springframework.org/schema/aop" xmlns:tx= "http://www.springframework.org/schema/tx" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http: //www.springframework.org/schema/beans http: //www.springframework.org/schema/beans/spring-beans-3.2.xsd http: //www.springframework.org/schema/aop http: //www.springframework.org/schema/aop/spring-aop-3.2.xsd http: //www.springframework.org/schema/tx http: //www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!-- 配置c3p0数据源 --> <bean id= "dataSourceOracle" class = "com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method= "close" > <property name= "driverClass" value= "${datasource.driver}" /> <property name= "jdbcUrl" value= "${datasource.url}" /> <property name= "user" value= "${datasource.username}" /> <property name= "password" value= "${datasource.password}" /> <property name= "acquireIncrement" value= "${c3p0.acquireIncrement}" /> <property name= "initialPoolSize" value= "${c3p0.initialPoolSize}" /> <property name= "minPoolSize" value= "${c3p0.minPoolSize}" /> <property name= "maxPoolSize" value= "${c3p0.maxPoolSize}" /> <property name= "maxIdleTime" value= "${c3p0.maxIdleTime}" /> <property name= "idleConnectionTestPeriod" value= "${c3p0.idleConnectionTestPeriod}" /> <property name= "maxStatements" value= "${c3p0.maxStatements}" /> <property name= "numHelperThreads" value= "${c3p0.numHelperThreads}" /> <property name= "preferredTestQuery" value= "${c3p0.preferredTestQuery}" /> <property name= "testConnectionOnCheckout" value= "${c3p0.testConnectionOnCheckout}" /> </bean> <bean id= "dataSourceMySQL" class = "com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method= "close" > <property name= "driverClass" value= "com.mysql.jdbc.Driver" /> <property name= "jdbcUrl" value= "jdbc:mysql://172.31.108.178:3306/world?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull" /> <property name= "user" value= "root" /> <property name= "password" value= "jp2011" /> <property name= "acquireIncrement" value= "${c3p0.acquireIncrement}" /> <property name= "initialPoolSize" value= "${c3p0.initialPoolSize}" /> <property name= "minPoolSize" value= "${c3p0.minPoolSize}" /> <property name= "maxPoolSize" value= "${c3p0.maxPoolSize}" /> <property name= "maxIdleTime" value= "${c3p0.maxIdleTime}" /> <property name= "idleConnectionTestPeriod" value= "${c3p0.idleConnectionTestPeriod}" /> <property name= "maxStatements" value= "${c3p0.maxStatements}" /> <property name= "numHelperThreads" value= "${c3p0.numHelperThreads}" /> <property name= "preferredTestQuery" value= "${c3p0.preferredTestQuery}" /> <property name= "testConnectionOnCheckout" value= "${c3p0.testConnectionOnCheckout}" /> </bean> <!-- 配置SqlSessionFactoryBean --> <bean id= "oracleSqlSessionFactory" class = "org.mybatis.spring.SqlSessionFactoryBean" > <property name= "dataSource" ref= "dataSourceOracle" /> <property name= "configLocation" value= "classpath:mybatis.xml" /> <!-- mapper和resultmap配置路径 --> <property name= "mapperLocations" > <list> <!-- 表示在com.hoo目录下的任意包下的resultmap包目录中,以-resultmap.xml或-mapper.xml结尾所有文件 --> <value>classpath:com/hoo/framework/mybatis/mybatis-common.xml</value> <value>classpath:com/hoo /**/ resultmap /*-resultmap.xml</value> <value>classpath:com/hoo/**/mapper/*-mapper.xml</value> <value>classpath:com/hoo/**/mapper/**/*-mapper.xml</value> </list> </property> </bean> <!-- 配置SqlSessionFactoryBean --> <bean id="mysqlSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSourceMySQL"/> <property name="configLocation" value="classpath:mybatis.xml"/> <!-- mapper和resultmap配置路径 --> <property name="mapperLocations"> <list> <!-- 表示在com.hoo目录下的任意包下的resultmap包目录中,以-resultmap.xml或-mapper.xml结尾所有文件 (oracle和mysql扫描的配置和路径不一样,如果是公共的都扫描 这里要区分下,不然就报错 找不到对应的表、视图)--> <value>classpath:com/hoo/framework/mybatis/mybatis-common.xml</value> <value>classpath:com/hoo/**/resultmap/*-mysql-resultmap.xml</value> <value>classpath:com/hoo/**/mapper/*-mysql-mapper.xml</value> <value>classpath:com/hoo/**/mapper/**/*-mysql-mapper.xml</value> <value>classpath:com/hoo/**/mapper/**/ multiple-datasource-mapper.xml</value> </list> </property> </bean> <!-- 配置自定义的SqlSessionTemplate模板,注入相关配置 --> <bean id= "sqlSessionTemplate" class = "com.hoo.framework.mybatis.support.CustomSqlSessionTemplate" > <constructor-arg ref= "oracleSqlSessionFactory" /> <property name= "targetSqlSessionFactorys" > <map> <entry value-ref= "oracleSqlSessionFactory" key= "oracle" /> <entry value-ref= "mysqlSqlSessionFactory" key= "mysql" /> </map> </property> </bean> <!-- 通过扫描的模式,扫描目录在com/hoo/任意目录下的mapper目录下,所有的mapper都需要继承SqlMapper接口的接口 --> <bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name= "basePackage" value= "com.hoo.**.mapper" /> <!-- 注意注入sqlSessionTemplate --> <property name= "sqlSessionTemplateBeanName" value= "sqlSessionTemplate" /> <property name= "markerInterface" value= "com.hoo.framework.mybatis.SqlMapper" /> </bean> </beans> |
上面的配置关键是在MapperScannerConfigurer中注入sqlSessionTemplate,这个要注意。当我们配置了多个SqlSessionFactoryBean的时候,就需要为MapperScannerConfigurer指定一个sqlSessionFactoryBeanName或是sqlSessionTemplateBeanName。一般情况下注入了sqlSessionTemplateBeanName对象,那sqlSessionFactory也就有值了。如果单独的注入了sqlSessionFactory那么程序会创建一个sqlSessionTemplate对象。我们可以看看代码SqlSessionFactoryDaoSupport对象的代码。如果你不喜欢使用扫描的方式,也可以注入sqlSessionTemplate或继承sqlSessionTemplate完成数据库操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSession sqlSession; private boolean externalSqlSession; public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (! this .externalSqlSession) { this .sqlSession = new SqlSessionTemplate(sqlSessionFactory); } } public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this .sqlSession = sqlSessionTemplate; this .externalSqlSession = true ; } ...... |
这段代码很明显,如果注入了sqlSessionTemplate上面的注入也就不会执行了。如果没有注入sqlSessionTemplate,那么会自动new一个sqlSessionTemplate对象。
3、编写相关测试接口和实现的mapper.xml
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
|
package com.hoo.server.datasource.mapper; import java.util.List; import java.util.Map; import com.hoo.framework.mybatis.SqlMapper; /** * <b>function:</b> MyBatis 多数据源 测试查询接口 * @author hoojo * @createDate 2013-10-10 下午04:18:08 * @file MultipleDataSourceMapper.java * @package com.hoo.server.datasource.mapper * @project SHMB * @blog http://blog.csdn.net/IBM_hoojo * @email hoojo_@126.com * @version 1.0 */ public interface MultipleDataSourceMapper extends SqlMapper { public List<Map<String, Object>> execute4MySQL() throws Exception; public List<Map<String, Object>> execute4Oracle() throws Exception; } multiple-datasource-mapper.xml <?xml version= "1.0" encoding= "UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace= "com.hoo.server.datasource.mapper.MultipleDataSourceMapper" > <select id= "execute4Oracle" resultType= "map" > <![CDATA[ SELECT * FROM deviceInfo_tab t where rownum < 10 ]]> </select> <select id= "execute4MySQL" resultType= "map" > <![CDATA[ SELECT * FROM city limit 2 ]]> </select> </mapper> |
上面分别查询oracle和mysql两个数据库中的table
4、测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@Autowired @Qualifier ( "multipleDataSourceMapper" ) private MultipleDataSourceMapper mapper; @Test public void testMapper() { CustomerContextHolder.setContextType(CustomerContextHolder.SESSION_FACTORY_MYSQL); try { trace(mapper.execute4MySQL()); } catch (Exception e1) { e1.printStackTrace(); } CustomerContextHolder.setContextType(CustomerContextHolder.SESSION_FACTORY_ORACLE); try { trace(mapper.execute4Oracle()); } catch (Exception e) { e.printStackTrace(); } } |
运行后发现能够顺利查询出数据。
如果你是重写SqlSessionDaoSupport,那么方法如下
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
|
package com.hoo.framework.mybatis.support; import java.util.Map; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionUtils; import org.mybatis.spring.support.SqlSessionDaoSupport; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; /** * <b>function:</b> MyBatis 动态SqlSessionFactory * @author hoojo * @createDate 2013-10-14 下午02:32:19 * @file DynamicSqlSessionDaoSupport.java * @package com.hoo.framework.mybatis.support * @project SHMB * @blog http://blog.csdn.net/IBM_hoojo * @email hoojo_@126.com * @version 1.0 */ public class DynamicSqlSessionDaoSupport extends SqlSessionDaoSupport implements ApplicationContextAware { private ApplicationContext applicationContext; private Map<Object, SqlSessionFactory> targetSqlSessionFactorys; private SqlSessionFactory defaultTargetSqlSessionFactory; private SqlSession sqlSession; @Override public final SqlSession getSqlSession() { SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(CustomerContextHolder.getContextType()); if (targetSqlSessionFactory != null ) { setSqlSessionFactory(targetSqlSessionFactory); } else if (defaultTargetSqlSessionFactory != null ) { setSqlSessionFactory(defaultTargetSqlSessionFactory); targetSqlSessionFactory = defaultTargetSqlSessionFactory; } else { targetSqlSessionFactory = (SqlSessionFactory) applicationContext.getBean(CustomerContextHolder.getContextType()); setSqlSessionFactory(targetSqlSessionFactory); } this .sqlSession = SqlSessionUtils.getSqlSession(targetSqlSessionFactory); return this .sqlSession; } @Override protected void checkDaoConfig() { //Assert.notNull(getSqlSession(), "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required"); } public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) { this .targetSqlSessionFactorys = targetSqlSessionFactorys; } public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) { this .defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this .applicationContext = applicationContext; } } |
主要重写getSqlSession方法,上面获取SqlSessionFactory的方法。
重写好了后就可以配置这个对象,配置代码如下
1
2
3
4
5
6
7
|
//每一个DAO由继承SqlSessionDaoSupport全部改为DynamicSqlSessionDaoSupport public class UserMapperDaoImpl extends DynamicSqlSessionDaoSupport implements UserDao { public int addUser(User user) { return this .getSqlSession().insert( "com.hoo.user.dao.UserDao.addUser" , user); } } |
在上面的配置文件中加入配置
1
2
3
4
5
6
7
8
9
10
|
<bean id= "baseDao" class = "com.hoo.framework.mybatis.support.DynamicSqlSessionDaoSupport" abstract = "true" lazy-init= "true" > <property name= "targetSqlSessionFactorys" > <map> <entry value-ref= "oracleSqlSessionFactory" key= "oracle" /> <entry value-ref= "mysqlSqlSessionFactory" key= "mysql" /> </map> </property> <property name= "defaultTargetSqlSessionFactory" ref= "oracleSqlSessionFactory" /> </bean> <bean id= "userMapperDao" class = "com.hoo.user.dao.impl.UserMapperDaoImpl" parent= "baseDao" /> |
就这样也可以利用DynamicSqlSessionDaoSupport来完成动态切换sqlSessionFactory对象,只需用在注入userMapperDao调用方法的时候设置下CustomerContextHolder的contextType即可。
三、总结
为了实现这个功能看了mybatis-spring-1.2.0.jar这个包的部分源代码,代码内容不是很多。所以看了下主要的代码,下面做些简单的介绍。
MapperScannerConfigurer这个类就是我们要扫描的Mapper接口的类,也就是basePackage中继承markerInterface配置的接口。可以看看ClassPathBeanDefinitionScanner、ClassPathMapperScanner中的doScan这个方法。它会扫描basePackage这个包下所有接口,在ClassPathScanningCandidateComponentProvider中有这个方法findCandidateComponents,它会找到所有的BeanDefinition。
最重要的一点是ClassPathMapperScanner中的doScan这个方法它会给这些接口创建一个MapperFactoryBean。并且会检查sqlSessionFactory和sqlSessionTemplate对象的注入情况。
image 所以我们配置扫描的方式也就相当于我们在配置文件中给每一个Mapper配置一个MapperFactoryBean一样。而这个MapperFactoryBean又继承SqlSessionDaoSupport。所以当初我想重写MapperScannerConfigurer中的postProcessBeanDefinitionRegistry方法,然后重写方法中的ClassPathMapperScanner中的doScan方法,将definition.setBeanClass(MapperFactoryBean.class);改成自己定义的MapperFactoryBean。最后以失败告终,因为这里是Spring装载扫描对象的时候都已经为这些对象创建好了代理、设置好了mapperInterface和注入需要的类。所以在调用相关操作数据库的API方法的时候,设置对应的SqlSessionFactory也是无效的。
辗转反侧我看到了SqlSessionTemplate这个类,它的功能相当于SqlSessionDaoSupport的实现类MapperFactoryBean。最为关键的是SqlSessionTemplate有一个拦截器SqlSessionInterceptor,它复制所有SqlSession的创建、提交、关闭,而且是在每个方法之前。这点在上面也提到过了!所以我们只需要在SqlSessionInterceptor方法中获取SqlSessionFactory的时候,在这之前调用下CustomerContextHolder.setContextType方法即可完成数据库的SqlSessionFactory的切换。而在MapperScannerConfigurer提供了注入SqlSessionFactory和sqlSessionTemplate的方法,如果注入了SqlSessionFactory系统将会new一个sqlSessionTemplate,而注入了sqlSessionTemplate就不会创建其他对象(见下面代码)。所以我们配置一个sqlSessionTemplate并注入到MapperScannerConfigurer中,程序将会使用这个sqlSessionTemplate。本文最后的实现方式就是这样完成的。
1
2
3
4
5
6
7
8
9
|
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (! this .externalSqlSession) { this .sqlSession = new SqlSessionTemplate(sqlSessionFactory); } } public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this .sqlSession = sqlSessionTemplate; this .externalSqlSession = true ; } |
以上所述是小编给大家介绍的Spring3 整合MyBatis3 配置多数据源动态选择SqlSessionFactory详细教程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!
原文链接:http://www.blogjava.net/hoojo/archive/2013/10/22/405488.html