Mybatis是java開(kāi)發(fā)者非常熟悉的ORM框架,Spring集成Mybatis更是我們的日常開(kāi)發(fā)姿勢(shì)。
本篇主要講Mybatis與Spring集成所做的事情,讓讀過(guò)本文的開(kāi)發(fā)者對(duì)Mybatis和Spring的集成過(guò)程,有清晰的理解。
注:若文中有錯(cuò)誤或其他疑問(wèn),歡迎留下評(píng)論。
以mybatis-spring-2.0.2
<https://github.com/mybatis/spring/tree/mybatis-spring-2.0.2>為例,工程劃分六個(gè)模塊。
1、annotation 模塊
?
?
定義了@MapperScan和@MapperScans,用于掃描mapper接口。以及mapper掃描注冊(cè)器(MapperScannerRegistrar),掃描注冊(cè)器實(shí)現(xiàn)了
ImportBeanDefinitionRegistrar接口, 在Spring容器啟動(dòng)時(shí)會(huì)運(yùn)行所有實(shí)現(xiàn)了這個(gè)接口的實(shí)現(xiàn)類(lèi),
注冊(cè)器內(nèi)部會(huì)注冊(cè)一系列MyBatis相關(guān)Bean。
2、batch 模塊
?
?
?
?
?
批處理相關(guān),基于優(yōu)秀的批處理框架Spring batch 封裝了三個(gè)批處理相關(guān)類(lèi):
* MyBatisBatchItemWriter(批量寫(xiě))
* MyBatisCursorItemReader(游標(biāo)讀)
* MyBatisPagingItemReader(分頁(yè)讀)
在使用Mybatis時(shí),方便的應(yīng)用Spring? batch,詳見(jiàn)?Spring-batch使用
<http://www.mybatis.org/spring/zh/batch.html>。
3、config模塊
?
解析、處理讀取到的配置信息。
4、mapper模塊
?
?
這里是處理mapper的地方:
ClassPathMapperScanner(根據(jù)路徑掃描Mapper接口)與MapperScannerConfigurer
配合,完成批量掃描mapper接口并注冊(cè)為MapperFactoryBean。
5、support 模塊
?
支持包,SqlSessionDaoSupport
?是一個(gè)抽象的支持類(lèi),用來(lái)為你提供?SqlSession調(diào)用getSqlSession()方法會(huì)得到一個(gè)SqlSessionTemplate。
6、transaction 模塊,以及凌亂類(lèi)
?
?
?
?
?
與Spring集成后,事務(wù)管理交由Spring來(lái)做。
還有包括異常轉(zhuǎn)換,以及非常重要的SqlSessionFactoryBean,在外散落著。
下面重點(diǎn)講述幾個(gè)核心部分:?
一、初始化相關(guān)
1)SqlSessionFactoryBean
在基礎(chǔ)的MyBatis中,通過(guò)SqlSessionFactoryBuilder創(chuàng)建SqlSessionFactory。
集成Spring后由SqlSessionFactoryBean來(lái)創(chuàng)建?! ?br>public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>...
需要注意SqlSessionFactoryBean實(shí)現(xiàn)了Spring的FactoryBean接口。這意味著由Spring最終創(chuàng)建不是
SqlSessionFactoryBean本身,而是 getObject()的結(jié)果。我們來(lái)看下getObject()
@Override public SqlSessionFactory getObject() throws Exception { if (this
.sqlSessionFactory ==null) { //配置加載完畢后,創(chuàng)建SqlSessionFactory
afterPropertiesSet(); }return this.sqlSessionFactory; }
getObject()最終返回了當(dāng)前類(lèi)的 SqlSessionFactory,因此,Spring 會(huì)在應(yīng)用啟動(dòng)時(shí)創(chuàng)建?SqlSessionFactory
,并以?sqlSessionFactory名稱放進(jìn)容器。
2)??兩個(gè)重要屬性:
1.?SqlSessionFactory?有一個(gè)唯一的必要屬性:用于 JDBC 的?
DataSource不能為空,這點(diǎn)在afterPropertisSet()中體現(xiàn)。
2.?configLocation,它用來(lái)指定 MyBatis 的 XML 配置文件路徑。通常只用來(lái)配置?
<settings>相關(guān)。其他均使用Spring方式配置
5 public void afterPropertiesSet() throws Exception { 6 //dataSource不能為空 7
notNull(dataSource, "Property 'dataSource' is required"); 8 //有默認(rèn)值,初始化 = new
SqlSessionFactoryBuilder() 9 notNull(sqlSessionFactoryBuilder, "Property
'sqlSessionFactoryBuilder' is required"); 10 //判斷configuration &&
configLocation有且僅有一個(gè) 11 state((configuration == null && configLocation == null)
||
!(configuration != null && configLocation != null), 12 "Property
'configuration' and 'configLocation' can not specified with together"); 13 //
調(diào)用build方法創(chuàng)建sqlSessionFactory 14 this.sqlSessionFactory =
buildSqlSessionFactory();15 }
buildSqlSessionFactory()方法比較長(zhǎng)所以,這里省略了一部分代碼,只展示主要過(guò)程,看得出在這里進(jìn)行了Mybatis相關(guān)配置的解析,完成了Mybatis核心配置類(lèi)Configuration的創(chuàng)建和填充,最終返回SqlSessionFactory。
1 protected SqlSessionFactory buildSqlSessionFactory() throws Exception { 2
3 final Configuration targetConfiguration; 4 5 XMLConfigBuilder
xmlConfigBuilder =null; 6 // 如果自定義了 Configuration,就用自定義的 7 if (this
.configuration !=null) { 8 targetConfiguration = this.configuration; 9 if
(targetConfiguration.getVariables() ==null) { 10
targetConfiguration.setVariables(this.configurationProperties); 11 } else if (
this.configurationProperties != null) { 12
targetConfiguration.getVariables().putAll(this.configurationProperties); 13 }
14 // 如果配置了原生配置文件路徑,則根據(jù)路徑創(chuàng)建Configuration對(duì)象 15 } else if (this.configLocation
!=null) { 16 xmlConfigBuilder = new XMLConfigBuilder(this
.configLocation.getInputStream()
, null, this.configurationProperties); 17 targetConfiguration =
xmlConfigBuilder.getConfiguration();18 } else {21 // 兜底,使用默認(rèn)的 22
targetConfiguration =new Configuration(); 23 //
如果configurationProperties存在,設(shè)置屬性 24 Optional.ofNullable(this
.configurationProperties).ifPresent(targetConfiguration::setVariables); } 26 //
解析別名,指定包 27 if (hasLength(this.typeAliasesPackage)) { 28 scanClasses(this
.typeAliasesPackage,this.typeAliasesSuperType).stream() 29 .filter(clazz ->
!clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface()) 30 .filter(clazz -> !
clazz.isMemberClass())
.forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias); 31 }
32 //解析插件 33 if (!isEmpty(this.plugins)) { 34 Stream.of(this
.plugins).forEach(plugin -> { 35 targetConfiguration.addInterceptor(plugin);38
}39 ... 40 //如果需要解決原生配置文件,此時(shí)開(kāi)始解析(即配置了configLocation) 41 if (xmlConfigBuilder !=
null) { 42 try { 43 xmlConfigBuilder.parse(); 44 ... //
有可能配置多個(gè),所以遍歷處理(2.0.0支持可重復(fù)注解) 52 if (this.mapperLocations != null) { 53 if (this
.mapperLocations.length == 0) {
for (Resource mapperLocation : this.mapperLocations) { 57 ... //
根據(jù)mapper路徑,加載所以mapper接口 62 XMLMapperBuilder xmlMapperBuilder = new
XMLMapperBuilder(mapperLocation.getInputStream(),63 targetConfiguration,
mapperLocation.toString(), targetConfiguration.getSqlFragments());64
xmlMapperBuilder.parse();65 //構(gòu)造SqlSessionFactory 70 return this
.sqlSessionFactoryBuilder.build(targetConfiguration);71 }
二、事務(wù)管理
1)事務(wù)管理器配置
MyBatis-Spring 允許 MyBatis 參與到 Spring 的事務(wù)管理中。 借助 Spring 的
DataSourceTransactionManager 實(shí)現(xiàn)事務(wù)管理?! ?br>/** 一、XML方式配置 **/ <bean id="transactionManager" class
="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <
constructor-argref="dataSource" /> </bean> /** 一、注解方式配置 **/ @Bean public
DataSourceTransactionManager transactionManager() { return new
DataSourceTransactionManager(dataSource()); }
注意:為事務(wù)管理器指定的?DataSource?必須和用來(lái)創(chuàng)建?SqlSessionFactoryBean?的是同一個(gè)數(shù)據(jù)源,否則事務(wù)管理器就無(wú)法工作了。
配置好 Spring 的事務(wù)管理器,你就可以在 Spring 中按你平時(shí)的方式來(lái)配置事務(wù)。并且支持 @Transactional 注解(聲明式事務(wù))和
AOP 風(fēng)格的配置。在事務(wù)處理期間,一個(gè)單獨(dú)的?SqlSession?對(duì)象將會(huì)被創(chuàng)建和使用。當(dāng)事務(wù)完成時(shí),這個(gè) session
會(huì)以合適的方式提交或回滾。無(wú)需DAO類(lèi)中無(wú)需任何額外操作,MyBatis-Spring 將透明地管理事務(wù)。
2)?編程式事務(wù):
推薦TransactionTemplate?方式,簡(jiǎn)潔,優(yōu)雅??墒÷詫?duì)?commit?和?rollback?方法的調(diào)用?! ?br>1 TransactionTemplate transactionTemplate = new
TransactionTemplate(transactionManager);2 transactionTemplate.execute(txStatus
-> { 3 userMapper.insertUser(user); 4 return null; 5 });
注意:這段代碼使用了一個(gè)映射器,換成SqlSession同理。
三、SqlSession
在MyBatis 中,使用?SqlSessionFactory?來(lái)創(chuàng)建?SqlSession
。通過(guò)它執(zhí)行映射的sql語(yǔ)句,提交或回滾連接,當(dāng)不再需要它的時(shí)候,可以關(guān)閉 session。使用 MyBatis-Spring 之后,我們不再需要直接使用?
SqlSessionFactory?了,因?yàn)槲覀兊腷ean 可以被注入一個(gè)線程安全的?SqlSession,它能基于 Spring
的事務(wù)配置來(lái)自動(dòng)提交、回滾、關(guān)閉 session。
SqlSessionTemplate?
SqlSessionTemplate 是SqlSession的實(shí)現(xiàn),是線程安全的,因此可以被多個(gè)DAO或映射器共享使用。也是
MyBatis-Spring 的核心。
四、映射器
1) 映射器的注冊(cè)
1 /** 2 *@MapperScan注解方式
3 */ 4 @Configuration 5 @MapperScan("org.mybatis.spring.sample.mapper") 6
public class AppConfig { 8 } 10 /** 11 *@MapperScanS注解 (since 2.0.0新增,java8
支持可重復(fù)注解)12 * 指定多個(gè)路徑可選用此種方式 13 */ 14 @Configuration 15
@MapperScans({@MapperScan("com.zto.test1"), @MapperScan("com.zto.test2.mapper"
)})16 public class AppConfig { 18 } <!-- MapperScannerConfigurer方式,批量掃描注冊(cè) --> <
beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name
="basePackage" value="com.zto.test.*" /> <property name
="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean>
無(wú)論使用以上哪種方式注冊(cè)映射器,最終mapper接口都將被注冊(cè)為
MapperFactoryBean。既然是FactoryBean,我們來(lái)跟它的getObject()方法看下。
2) MapperFactoryBean源碼解析
1.查找MapperFactoryBean.getObject()
1 /** 2 * 通過(guò)接口類(lèi)型,獲取mapper 3 * {@inheritDoc} 4 */ 5 @Override 6 public T
getObject()throws Exception { 7 //getMapper 是一個(gè)抽象方法 8 return
getSqlSession().getMapper(this.mapperInterface); 9 }
2.查看實(shí)現(xiàn)類(lèi),SqlSessionTemplate.getMapper()
(
為什么是SqlSessionTemplate,而不是默認(rèn)的DefaultSqlSession?SqlSessionTemplate是整合包的核心,是線程安全的SqlSession實(shí)現(xiàn),是我們@Autowired
mapper接口編程的基礎(chǔ) )
4 @Override 5 public <T> T getMapper(Class<T> type) { 6 return
getConfiguration().getMapper(type,this); 7 }
3.調(diào)用Configuration.getMapper()
1 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 2 return
mapperRegistry.getMapper(type, sqlSession);3 }
4.調(diào)用MapperRegistry.getMapper()
1 @SuppressWarnings("unchecked") 2 public <T> T getMapper(Class<T> type,
SqlSession sqlSession) { 3 final MapperProxyFactory<T> mapperProxyFactory =
(MapperProxyFactory<T>) knownMappers.get(type); 4 if (mapperProxyFactory ==
null) { 5 throw new BindingException("Type " + type + " is not known to the
MapperRegistry."); 6 } 7 try { 8 return
mapperProxyFactory.newInstance(sqlSession); 9 } catch (Exception e) { 10 throw
new BindingException("Error getting mapper instance. Cause: " + e, e); 11 } 12
}
5.調(diào)用MapperProxyFactory.newInstance()
1 @SuppressWarnings("unchecked") 2 protected T newInstance(MapperProxy<T>
mapperProxy) {3 return (T)
Proxy.newProxyInstance(mapperInterface.getClassLoader(),new Class[] {
mapperInterface }, mapperProxy);4 }
最終看到動(dòng)態(tài)代理生成了一個(gè)新的代理實(shí)例返回了,也就是說(shuō),我們使用@Autowired
注解進(jìn)來(lái)一個(gè)mapper接口,每次使用時(shí)都會(huì)由代理生成一個(gè)新的實(shí)例。
為什么在Mybatis中SqlSession是方法級(jí)的,Mapper是方法級(jí)的,在集成Spring后卻可以注入到類(lèi)中使用?
因?yàn)樵贛ybatis-Spring中所有mapper被注冊(cè)為FactoryBean,每次調(diào)用都會(huì)執(zhí)行g(shù)etObject(),返回新實(shí)例。
五、總結(jié)
MyBatis集成Spring后,Spring侵入了Mybatis的初始化和mapper綁定,具體就是:
1)Cofiguration的實(shí)例化是讀取Spring的配置文件(注解、配置文件),而不是mybatis-config.xml
2)mapper對(duì)象是方法級(jí)別的,Spring通過(guò)FactoryBean巧妙地解決了這個(gè)問(wèn)題
3)事務(wù)交由Spring管理
注:如對(duì)文中內(nèi)容有疑問(wèn),歡迎留下評(píng)論共同探討。
?
熱門(mén)工具 換一換
