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来创建。   

  1. public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>...

  需要注意SqlSessionFactoryBean实现了Spring的FactoryBean接口。这意味着由Spring最终创建不是SqlSessionFactoryBean本身,而是 getObject()的结果。我们来看下getObject()

  1. @Override
  2. public SqlSessionFactory getObject() throws Exception {
  3. if (this.sqlSessionFactory == null) {
  4. //配置加载完毕后,创建SqlSessionFactory
  5. afterPropertiesSet();
  6. }
  7. return this.sqlSessionFactory;
  8. }

  getObject()最终返回了当前类的 SqlSessionFactory,因此,Spring 会在应用启动时创建 SqlSessionFactory,并以 sqlSessionFactory名称放进容器。

  2)  两个重要属性:

    1. SqlSessionFactory 有一个唯一的必要属性:用于 JDBC 的 DataSource不能为空,这点在afterPropertisSet()中体现。

    2. configLocation,它用来指定 MyBatis 的 XML 配置文件路径。通常只用来配置 <settings>相关。其他均使用Spring方式配置

  1. public void afterPropertiesSet() throws Exception {
  2. //dataSource不能为空
  3. notNull(dataSource, "Property 'dataSource' is required");
  4. //有默认值,初始化 = new SqlSessionFactoryBuilder()
  5. notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
  6. //判断configuration && configLocation有且仅有一个
  7. state((configuration == null && configLocation == null) ||
              !(configuration != null && configLocation != null),
  8. "Property 'configuration' and 'configLocation' can not specified with together");
  9. //调用build方法创建sqlSessionFactory
  10. this.sqlSessionFactory = buildSqlSessionFactory();
  11. }

     buildSqlSessionFactory()方法比较长所以,这里省略了一部分代码,只展示主要过程,看得出在这里进行了Mybatis相关配置的解析,完成了Mybatis核心配置类Configuration的创建和填充,最终返回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.    // 如果配置了原生配置文件路径,则根据路径创建Configuration对象
  15. } else if (this.configLocation != null) {
  16. xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream()
            , null, this.configurationProperties);
  17. targetConfiguration = xmlConfigBuilder.getConfiguration();
  18. } else {    // 兜底,使用默认的
  19. targetConfiguration = new Configuration();
  20.    //如果configurationProperties存在,设置属性
  21.    Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables); }
  22. //解析别名,指定包   
  23. if (hasLength(this.typeAliasesPackage)) {
  24. scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
  25. .filter(clazz -> !clazz.isAnonymousClass())
          .filter(clazz -> !clazz.isInterface())
  26. .filter(clazz -> !clazz.isMemberClass())
          .forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
  27. }
  28. //解析插件
  29. if (!isEmpty(this.plugins)) {
  30. Stream.of(this.plugins).forEach(plugin -> {
  31. targetConfiguration.addInterceptor(plugin); }
  32. ...
  33. //如果需要解决原生配置文件,此时开始解析(即配置了configLocation)
  34. if (xmlConfigBuilder != null) {
  35. try {
  36. xmlConfigBuilder.parse();
  37. 44    ... //有可能配置多个,所以遍历处理(2.0.0支持可重复注解)
  38. if (this.mapperLocations != null) {
  39. if (this.mapperLocations.length == 0) {
          for (Resource mapperLocation : this.mapperLocations) {
  40. ... //根据mapper路径,加载所以mapper接口
  41. XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
  42. targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
  43. xmlMapperBuilder.parse();
  44.  //构造SqlSessionFactory
  45. return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  46. }

  二、事务管理

  1)事务管理器配置

    MyBatis-Spring 允许 MyBatis 参与到 Spring 的事务管理中。 借助 Spring 的 DataSourceTransactionManager 实现事务管理。  

  1. /** 一、XML方式配置 **/
  2. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  3. <constructor-arg ref="dataSource" />
  4. </bean>
  5. /** 一、注解方式配置 **/
  6. @Bean
  7. public DataSourceTransactionManager transactionManager() {
  8. return new DataSourceTransactionManager(dataSource());
  9. }
    注意:为事务管理器指定的 DataSource 必须和用来创建 SqlSessionFactoryBean 的是同一个数据源,否则事务管理器就无法工作了。

  配置好 Spring 的事务管理器,你就可以在 Spring 中按你平时的方式来配置事务。并且支持 @Transactional 注解(声明式事务)和 AOP 风格的配置。在事务处理期间,一个单独的 SqlSession 对象将会被创建和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。无需DAO类中无需任何额外操作,MyBatis-Spring 将透明地管理事务。

  2) 编程式事务:

  推荐TransactionTemplate 方式,简洁,优雅。可省略对 commit 和 rollback 方法的调用。    

  1. TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
  2. transactionTemplate.execute(txStatus -> {
  3. userMapper.insertUser(user);
  4. return null;
  5. });
    注意:这段代码使用了一个映射器,换成SqlSession同理。

  三、SqlSession

  在MyBatis 中,使用 SqlSessionFactory 来创建 SqlSession。通过它执行映射的sql语句,提交或回滚连接,当不再需要它的时候,可以关闭 session。使用 MyBatis-Spring 之后,我们不再需要直接使用 SqlSessionFactory 了,因为我们的bean 可以被注入一个线程安全的 SqlSession,它能基于 Spring 的事务配置来自动提交、回滚、关闭 session。

  SqlSessionTemplate  

  SqlSessionTemplate 是SqlSession的实现,是线程安全的,因此可以被多个DAO或映射器共享使用。也是 MyBatis-Spring 的核心。

  四、映射器

  1) 映射器的注册  

  1. /**
  2. *@MapperScan注解方式
    */
  3. @Configuration
  4. @MapperScan("org.mybatis.spring.sample.mapper")
  5. public class AppConfig {
  6. }
  7. /**
  8. *@MapperScanS注解 (since 2.0.0新增,java8 支持可重复注解)
  9. * 指定多个路径可选用此种方式
  10. */
  11. @Configuration
  12. @MapperScans({@MapperScan("com.zto.test1"), @MapperScan("com.zto.test2.mapper")})
  13. public class AppConfig {
  14. }
  1. <!-- MapperScannerConfigurer方式,批量扫描注册 -->
  2. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  3. <property name="basePackage" value="com.zto.test.*" />
  4. <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
  5. </bean>

  无论使用以上哪种方式注册映射器,最终mapper接口都将被注册为MapperFactoryBean。既然是FactoryBean,我们来跟它的getObject()方法看下。

  2) MapperFactoryBean源码解析

    1.查找MapperFactoryBean.getObject()  

  1. /**
  2. * 通过接口类型,获取mapper
  3. * {@inheritDoc}
  4. */
  5. @Override
  6. public T getObject() throws Exception {
  7. //getMapper 是一个抽象方法
  8. return getSqlSession().getMapper(this.mapperInterface);
  9. }

    2.查看实现类,SqlSessionTemplate.getMapper()

    ( 为什么是SqlSessionTemplate,而不是默认的DefaultSqlSession?SqlSessionTemplate是整合包的核心,是线程安全的SqlSession实现,是我们@Autowired mapper接口编程的基础 )

  1. @Override
  2. public <T> T getMapper(Class<T> type) {
  3. return getConfiguration().getMapper(type, this);
  4. }

    3.调用Configuration.getMapper()  

  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2. return mapperRegistry.getMapper(type, sqlSession);
  3. }

    4.调用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.调用MapperProxyFactory.newInstance()  

  1. @SuppressWarnings("unchecked")
  2. protected T newInstance(MapperProxy<T> mapperProxy) {
  3. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  4. }

    最终看到动态代理生成了一个新的代理实例返回了,也就是说,我们使用@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集成时都做了什么?的更多相关文章

  1. mybatis与Spring集成(Aop整合PagerAspect插件)

    目的: Mybatis与spring集成 Aop整合pagehelper插件 Mybatis与spring集成 导入pom依赖 <?xml version="1.0" enc ...

  2. 重构Mybatis与Spring集成的SqlSessionFactoryBean(1)

    一般来说,修改框架的源代码是极其有风险的,除非万不得已,否则不要去修改.但是今天却小心翼翼的重构了Mybatis官方提供的与Spring集成的SqlSessionFactoryBean类,一来是抱着试 ...

  3. Mybatis与Spring集成(易百教程)

    整个Mybatis与Spring集成示例要完成的步骤如下: 1.示例功能描述 2.创建工程 3.数据库表结构及数据记录 4.实例对象 5.配置文件 6.测试执行,输出结果 1.示例功能描述 在本示例中 ...

  4. struts2与spring集成时action的class属性值意义

    struts2单独使用时action由struts2自己负责创建:与spring集成时,action实例由spring负责创建(依赖注入).这导致在两种情况下struts.xml配置文件的略微差异. ...

  5. 重构Mybatis与Spring集成的SqlSessionFactoryBean(2)

    三.代码重构 1.先使用Eclipse把buildSqlSessionFactory()方法中众多的if换成小函数 protected SqlSessionFactory buildSqlSessio ...

  6. mybatis与spring整合时读取properties问题的解决

    在学习mybatis与spring整合是,想从外部引用一个db.properties数据库配置文件,在配置文件中使用占位符进行引用,如下: <context:property-placehold ...

  7. MyBatis与Spring集成

    beans.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="htt ...

  8. MHA在监控和故障转移时都做了什么

    转自 https://blog.csdn.net/ashic/article/details/75645479 以下是MHA(masterha_manager)在监控和故障切换上的基本流程 验证复制配 ...

  9. struts2与spring集成时,关于class属性及成员bean自动注入的问题

    http://blog.csdn.net/sun_zhicheng/article/details/24232129

随机推荐

  1. 前端js倒计时(精确到毫秒)

    话不多说,直接上代码: 有需要直接拿走, <html> <head> <style> div{ width:100%; text-align:center; fon ...

  2. PATA 1027 Colors In Mars

    #include <cstdio> char radix[13] = {'0','1','2','3','4','5','6','7','8','9','A','B','C'}; int ...

  3. JAVA Stirng.format 使用理解

    JAVA Stirng.format 使用理解前言:项目中需要对一些字符串处理发现format方法的神奇之处一.api才是王道第一种二参使用①public static String format(S ...

  4. 关于c++中的Debug以及runtime_error之segment_fault

    差不多每次编一些竞赛类的程序都会至少给我报一次runtime_error(运行时错误).这或许也是广大OIer心中永远的痛.~_~ 本文主要讨论如何对runtime_error以及其中的segment ...

  5. EditPlus VC2010 and 2008 C/C++配置

    源自:http://blog.csdn.net/weiling_shen/archive/2010/03/26/5421017.aspx 对于2010跟2008差不多,只需相应的修改一下路径即可:如2 ...

  6. CentOS7 使用 kubeadm 搭建 k8s 集群

    一 安装Docker-CE 前言 Docker 使用越来越多,安装也很简单,本次记录一下基本的步骤. Docker 目前支持 CentOS 7 及以后的版本,内核要求至少为 3.10. Docker ...

  7. 测试调试-利用fiddler修改response返回结果

    测试前端过程中,经常需要验证各种功能状态.不同数据层级等返回后的展示效果.一般会通过以下三种方式进行测试: 1.构造满足条件的测试数据:(耗时费力) 2.修改数据库:(前提需要了解数据库数据存储.沟通 ...

  8. 【烂笔头】常用adb命令记录

    前言    Android的adb提供了很多命令,功能很强大,可以为开发和调试带来很大的便利.当然本文并不是介绍各种命令的文章,而是用于记录在平时工作中需要经常使用的命令,方便平时工作时使用,所以以后 ...

  9. 图解AQS原理之ReentrantLock详解-非公平锁

    概述 并发编程中,ReentrantLock的使用是比较多的,包括之前讲的LinkedBlockingQueue和ArrayBlockQueue的内部都是使用的ReentrantLock,谈到它又不能 ...

  10. Altium Designer设计PCB--如何设置铺铜与导线或过孔的间距

    笑话: 到银行汇款,车临时停路边上. 为了怕交警罚就把朋友留下看车,跟他说有查车的过来了告诉我一声. 进去几分钟果然有交警来了. 那个朋友风风火火地闯进银行大声吼道:“大哥,警察来了,快走啊!” 偌大 ...