Mybatis与Spring集成时都做了什么?
Mybatis是java开发者非常熟悉的ORM框架,Spring集成Mybatis更是我们的日常开发姿势。
本篇主要讲Mybatis与Spring集成所做的事情,让读过本文的开发者对Mybatis和Spring的集成过程,有清晰的理解。
注:若文中有错误或其他疑问,欢迎留下评论。
以mybatis-spring-2.0.2为例,工程划分六个模块。
1、annotation 模块
定义了@MapperScan和@MapperScans,用于扫描mapper接口。以及mapper扫描注册器(MapperScannerRegistrar),扫描注册器实现了 ImportBeanDefinitionRegistrar接口, 在Spring容器启动时会运行所有实现了这个接口的实现类,
注册器内部会注册一系列MyBatis相关Bean。
2、batch 模块
批处理相关,基于优秀的批处理框架Spring batch 封装了三个批处理相关类:
- MyBatisBatchItemWriter(批量写)
- MyBatisCursorItemReader(游标读)
- MyBatisPagingItemReader(分页读)
在使用Mybatis时,方便的应用Spring batch,详见 Spring-batch使用。
3、config模块
解析、处理读取到的配置信息。
4、mapper模块
这里是处理mapper的地方:ClassPathMapperScanner(根据路径扫描Mapper接口)与MapperScannerConfigurer 配合,完成批量扫描mapper接口并注册为MapperFactoryBean。
5、support 模块
支持包,SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession调用getSqlSession()方法会得到一个SqlSessionTemplate。
6、transaction 模块,以及凌乱类
与Spring集成后,事务管理交由Spring来做。
还有包括异常转换,以及非常重要的SqlSessionFactoryBean,在外散落着。
下面重点讲述几个核心部分:
一、初始化相关
1)SqlSessionFactoryBean
在基础的MyBatis中,通过SqlSessionFactoryBuilder创建SqlSessionFactory。集成Spring后由SqlSessionFactoryBean来创建。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>...
需要注意SqlSessionFactoryBean实现了Spring的FactoryBean接口。这意味着由Spring最终创建不是SqlSessionFactoryBean本身,而是 getObject()的结果。我们来看下getObject()
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
//配置加载完毕后,创建SqlSessionFactory
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
getObject()最终返回了当前类的 SqlSessionFactory,因此,Spring 会在应用启动时创建 SqlSessionFactory,并以 sqlSessionFactory名称放进容器。
2) 两个重要属性:
1. SqlSessionFactory 有一个唯一的必要属性:用于 JDBC 的 DataSource不能为空,这点在afterPropertisSet()中体现。
2. configLocation,它用来指定 MyBatis 的 XML 配置文件路径。通常只用来配置 <settings>相关。其他均使用Spring方式配置
public void afterPropertiesSet() throws Exception {
//dataSource不能为空
notNull(dataSource, "Property 'dataSource' is required");
//有默认值,初始化 = new SqlSessionFactoryBuilder()
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
//判断configuration && configLocation有且仅有一个
state((configuration == null && configLocation == null) ||
!(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
//调用build方法创建sqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
buildSqlSessionFactory()方法比较长所以,这里省略了一部分代码,只展示主要过程,看得出在这里进行了Mybatis相关配置的解析,完成了Mybatis核心配置类Configuration的创建和填充,最终返回SqlSessionFactory。
protected SqlSessionFactory buildSqlSessionFactory() throws Exception { final Configuration targetConfiguration; XMLConfigBuilder xmlConfigBuilder = null;
// 如果自定义了 Configuration,就用自定义的
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
// 如果配置了原生配置文件路径,则根据路径创建Configuration对象
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream()
, null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else { // 兜底,使用默认的
targetConfiguration = new Configuration();
//如果configurationProperties存在,设置属性
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables); }
//解析别名,指定包
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass())
.forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
//解析插件
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin); }
...
//如果需要解决原生配置文件,此时开始解析(即配置了configLocation)
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
44 ... //有可能配置多个,所以遍历处理(2.0.0支持可重复注解)
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
for (Resource mapperLocation : this.mapperLocations) {
... //根据mapper路径,加载所以mapper接口
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
//构造SqlSessionFactory
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
二、事务管理
1)事务管理器配置
MyBatis-Spring 允许 MyBatis 参与到 Spring 的事务管理中。 借助 Spring 的 DataSourceTransactionManager 实现事务管理。
/** 一、XML方式配置 **/
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
/** 一、注解方式配置 **/
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
注意:为事务管理器指定的 DataSource 必须和用来创建 SqlSessionFactoryBean 的是同一个数据源,否则事务管理器就无法工作了。
配置好 Spring 的事务管理器,你就可以在 Spring 中按你平时的方式来配置事务。并且支持 @Transactional 注解(声明式事务)和 AOP 风格的配置。在事务处理期间,一个单独的 SqlSession 对象将会被创建和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。无需DAO类中无需任何额外操作,MyBatis-Spring 将透明地管理事务。
2) 编程式事务:
推荐TransactionTemplate 方式,简洁,优雅。可省略对 commit 和 rollback 方法的调用。
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(txStatus -> {
userMapper.insertUser(user);
return null;
});
注意:这段代码使用了一个映射器,换成SqlSession同理。
三、SqlSession
在MyBatis 中,使用 SqlSessionFactory 来创建 SqlSession。通过它执行映射的sql语句,提交或回滚连接,当不再需要它的时候,可以关闭 session。使用 MyBatis-Spring 之后,我们不再需要直接使用 SqlSessionFactory 了,因为我们的bean 可以被注入一个线程安全的 SqlSession,它能基于 Spring 的事务配置来自动提交、回滚、关闭 session。
SqlSessionTemplate
SqlSessionTemplate 是SqlSession的实现,是线程安全的,因此可以被多个DAO或映射器共享使用。也是 MyBatis-Spring 的核心。
四、映射器
1) 映射器的注册
/**
*@MapperScan注解方式
*/
@Configuration
@MapperScan("org.mybatis.spring.sample.mapper")
public class AppConfig {
}
/**
*@MapperScanS注解 (since 2.0.0新增,java8 支持可重复注解)
* 指定多个路径可选用此种方式
*/
@Configuration
@MapperScans({@MapperScan("com.zto.test1"), @MapperScan("com.zto.test2.mapper")})
public class AppConfig {
}
<!-- MapperScannerConfigurer方式,批量扫描注册 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.zto.test.*" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
无论使用以上哪种方式注册映射器,最终mapper接口都将被注册为MapperFactoryBean。既然是FactoryBean,我们来跟它的getObject()方法看下。
2) MapperFactoryBean源码解析
1.查找MapperFactoryBean.getObject()
/**
* 通过接口类型,获取mapper
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
//getMapper 是一个抽象方法
return getSqlSession().getMapper(this.mapperInterface);
}
2.查看实现类,SqlSessionTemplate.getMapper()
( 为什么是SqlSessionTemplate,而不是默认的DefaultSqlSession?SqlSessionTemplate是整合包的核心,是线程安全的SqlSession实现,是我们@Autowired mapper接口编程的基础 )
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
3.调用Configuration.getMapper()
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
4.调用MapperRegistry.getMapper()
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
5.调用MapperProxyFactory.newInstance()
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
最终看到动态代理生成了一个新的代理实例返回了,也就是说,我们使用@Autowired 注解进来一个mapper接口,每次使用时都会由代理生成一个新的实例。
为什么在Mybatis中SqlSession是方法级的,Mapper是方法级的,在集成Spring后却可以注入到类中使用?
因为在Mybatis-Spring中所有mapper被注册为FactoryBean,每次调用都会执行getObject(),返回新实例。
五、总结
MyBatis集成Spring后,Spring侵入了Mybatis的初始化和mapper绑定,具体就是:
1)Cofiguration的实例化是读取Spring的配置文件(注解、配置文件),而不是mybatis-config.xml
2)mapper对象是方法级别的,Spring通过FactoryBean巧妙地解决了这个问题
3)事务交由Spring管理
注:如对文中内容有疑问,欢迎留下评论共同探讨。
Mybatis与Spring集成时都做了什么?的更多相关文章
- mybatis与Spring集成(Aop整合PagerAspect插件)
目的: Mybatis与spring集成 Aop整合pagehelper插件 Mybatis与spring集成 导入pom依赖 <?xml version="1.0" enc ...
- 重构Mybatis与Spring集成的SqlSessionFactoryBean(1)
一般来说,修改框架的源代码是极其有风险的,除非万不得已,否则不要去修改.但是今天却小心翼翼的重构了Mybatis官方提供的与Spring集成的SqlSessionFactoryBean类,一来是抱着试 ...
- Mybatis与Spring集成(易百教程)
整个Mybatis与Spring集成示例要完成的步骤如下: 1.示例功能描述 2.创建工程 3.数据库表结构及数据记录 4.实例对象 5.配置文件 6.测试执行,输出结果 1.示例功能描述 在本示例中 ...
- struts2与spring集成时action的class属性值意义
struts2单独使用时action由struts2自己负责创建:与spring集成时,action实例由spring负责创建(依赖注入).这导致在两种情况下struts.xml配置文件的略微差异. ...
- 重构Mybatis与Spring集成的SqlSessionFactoryBean(2)
三.代码重构 1.先使用Eclipse把buildSqlSessionFactory()方法中众多的if换成小函数 protected SqlSessionFactory buildSqlSessio ...
- mybatis与spring整合时读取properties问题的解决
在学习mybatis与spring整合是,想从外部引用一个db.properties数据库配置文件,在配置文件中使用占位符进行引用,如下: <context:property-placehold ...
- MyBatis与Spring集成
beans.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="htt ...
- MHA在监控和故障转移时都做了什么
转自 https://blog.csdn.net/ashic/article/details/75645479 以下是MHA(masterha_manager)在监控和故障切换上的基本流程 验证复制配 ...
- struts2与spring集成时,关于class属性及成员bean自动注入的问题
http://blog.csdn.net/sun_zhicheng/article/details/24232129
随机推荐
- iOS开发系列之性能优化(上)
本篇主要记录一下我对界面优化上的一些探索.关于时间优化的探索将会在中篇里进行介绍.下篇将主要介绍一些耗电优化.安装包瘦身的探索. ### 1.卡顿原理 要了解卡顿原理,需要对帧缓冲区.垂直同步.CPU ...
- K2工作流引擎Demo
---恢复内容开始--- 以前的工作都是电商网站形式的,从未接触过工作流相关工作,新公司是传统制造业行业,我进的这个组又是做工作流这块相关工作的,所以避免不了和工作流打交道. 这边工作流主要用K2来做 ...
- DRF 版本、认证、权限、限制、解析器和渲染器
目录 一.DRF之版本控制 为什么要有版本控制? DRF提供的版本控制方案 版本的使用 全局配置 局部配置(使用较少) 二.DRF之认证 内置的认证 步骤 三.DRF之权限 1.自定义一个权限类 2. ...
- 使用字蛛教程以及遇到的bug
前言: 前段时间刚完成一个外项目,歇了几天,老大让我看看公司的官网,优化一下,发现移动端的字体下载特别慢,才发现引用了字体包,一个字体包就达到了11M,想着既然有了图片压缩,那么应该有字体压缩,所以百 ...
- NOIP2015斗地主题解 7.30考试
问题 B: NOIP2015 斗地主 时间限制: 3 Sec 内存限制: 1024 MB 题目描述 牛牛最近迷上了一种叫斗地主的扑克游戏.斗地主是一种使用黑桃.红心.梅花.方片的A到K加上大小王的共 ...
- infiniband 网卡驱动安装
硬件:Mellanox InfiniBand,主要包括 HCA(主机通道适配器)和交换机两部分软件:CentOS 6.4MLNX_OFED_LINUX-2.1-1.0.0-rhel6.4-x86_64 ...
- WinForm控件之【MaskedTextBox】
基本介绍 掩码文本控件,使用掩码来区分用户输入文本是否正确. 常设置属性 BeepOnError:指示键入无效字符是控件是否发出系统提示音: CutCopyMaskFormat:设置控件文本值复制到剪 ...
- MyBatis 接口多参数的处理方法
From<MyBatis从入门到精通> 1.接口类中增加的方法: /* 2.7 多个接口参数的用法 多个参数时,可以选取的方案有:使用Map类型或者使用@Param注解 使用Map类型作为 ...
- [记录]优化Linux 的内核参数来提高服务器并发处理能力
优化Linux 的内核参数来提高服务器并发处理能力PS:在服务器硬件资源额定有限的情况下,最大的压榨服务器的性能,提高服务器的并发处理能力,是很多运维技术人员思考的问题.要提高Linux 系统下的负载 ...
- python3.5学习笔记(说明)
本内容是自己在学习python过程中总结的知识点,只用于学习和交流,请勿用作商业用途,部分内容来自网络,如有侵权,联系删除.