一、摘要
這篇文章將介紹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 [email protected] * @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 [email protected] * @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 [email protected] * @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 [email protected] * @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