Mybatis与Spring集成时都做了什么?

  Mybatis是java开发者非常熟悉的ORM框架,Spring集成Mybatis更是我们的日常开发姿势。

  本篇主要讲Mybatis与Spring集成所做的事情,让读过本文的开发者对Mybatis和Spring的集成过程,有清晰的理解。

  以mybatis-spring-2.0.2为例,工程划分六个模块。

一、annotation 模块

  定义了@MapperScan和@MapperScans,用于注解方式扫描mapper接口。以及mapper扫描注册器(MapperScannerRegistrar),扫描注册器实现了 ImportBeanDefinitionRegistrar接口,在Spring容器启动时会运行所有实现了这个接口的实现类,注册器内部会注册一系列MyBatis相关Bean。

二、batch 模块

  批处理相关,基于优秀的批处理框架Spring batch 封装了三个批处理相关类:

    MyBatisBatchItemWriter(批量写)

    MyBatisCursorItemReader(游标读)

    MyBatisPagingItemReader(分页读)

  在使用Mybatis时,方便的应用Spring  batch,详见 Spring-batch使用

三、config模块

  解析、处理读取到的配置信息。

四、mapper模块

  这里是处理mapper的地方了:

    ClassPathMapperScanner(根据配置路径扫描加载所有Mapper接口)

    MapperScannerConfigurer 批量扫描mapper接口注册为MapperFactoryBean,后面重点讲述。

五、support 模块

  支持包,SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession调用getSqlSession()方法会得到一个SqlSessionTemplate。

六、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方式配置

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

     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 {21    // 兜底,使用默认的
22       targetConfiguration = new Configuration();
23    //如果configurationProperties存在,设置属性
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 //如果需要解决原生配置文件,此时开始解析(即配置了configLocation)
41 if (xmlConfigBuilder != null) {
42   try {
43     xmlConfigBuilder.parse();
44    ...  //有可能配置多个,所以遍历处理(2.0.0支持可重复注解)
52 if (this.mapperLocations != null) {
53     if (this.mapperLocations.length == 0) {      for (Resource mapperLocation : this.mapperLocations) {
57        ...  //根据mapper路径,加载所以mapper接口
62           XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
63               targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
64           xmlMapperBuilder.parse();
65        //构造SqlSessionFactory
70   return this.sqlSessionFactoryBuilder.build(targetConfiguration);
71 }

  二、事务管理

  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 方法的调用。    

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  */
 4 @Configuration
 5 @MapperScan("org.mybatis.spring.sample.mapper")
 6 public class AppConfig {
 8 }
10 /**
11  *@MapperScanS注解 (since 2.0.0新增,java8 支持可重复注解)
12  * 指定多个路径可选用次种方式
13  */
14 @Configuration
15 @MapperScans({@MapperScan("com.zto.test1"), @MapperScan("com.zto.test2.mapper")})
16 public class AppConfig {
18 }
<!-- 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()  

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接口编程的基础 )

4   @Override
5   public <T> T getMapper(Class<T> type) {
6     return getConfiguration().getMapper(type, this);
7   }

    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管理

    注:如文中有错误或对文中内容有其他疑问,欢迎留下评论。

原文地址:https://www.cnblogs.com/xwy6/p/11279518.html

时间: 2024-10-10 08:13:58

Mybatis与Spring集成时都做了什么?的相关文章

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

目的: Mybatis与spring集成 Aop整合pagehelper插件 Mybatis与spring集成 导入pom依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

Mybatis与Spring集成(易百教程)

整个Mybatis与Spring集成示例要完成的步骤如下: 1.示例功能描述 2.创建工程 3.数据库表结构及数据记录 4.实例对象 5.配置文件 6.测试执行,输出结果 1.示例功能描述 在本示例中,需要完成这样的一个简单功能,即,指定一个用户(ID=1),查询出这个用户的基本信息,并关联查询这个用户的所有订单. 2.创建工程 首先创建一个工程的名称为:mybatis07-spring,在 src 源代码目录下建立文件夹 config,并将原来的 mybatis 配置文件 Configurat

重构Mybatis与Spring集成的SqlSessionFactoryBean(上)

一般来说,修改框架的源代码是极其有风险的,除非万不得已,否则不要去修改.但是今天却小心翼翼的重构了Mybatis官方提供的与Spring集成的SqlSessionFactoryBean类,一来是抱着试错的心态,二来也的确是有现实需要. 先说明两点: 通常来讲,重构是指不改变功能的情况下优化代码,但本文所说的重构也包括了添加功能 本文使用的主要jar包(版本):spring-*-4.3.3.RELEASE.jar.mybatis-3.4.1.jar.mybatis-spring-1.3.0.jar

深入浅出MyBatis:MyBatis与Spring集成及实用场景

本系列是「深入浅出MyBatis:技术原理与实践」书籍的总结笔记. 本篇是「深入浅出MyBatis」系列的最后一篇,主要介绍与Spring的集成,以及工作中的一些实用场景. 介绍之前,先整体总结下该系列的内容和写作思路. MyBatis是一个框架,封装了数据库相关的操作,给我们开发人员带来了极大地便利,相对于Hibernate,有很大的灵活性和扩展性,在高并发高性能应用中,这点很重要. 首先介绍了JDBC的规范,了解我们最原始最熟悉的操作数据库的方式,MyBatis就是在此基础上进行封装和抽象.

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

三.代码重构 1.先使用Eclipse把buildSqlSessionFactory()方法中众多的if换成小函数 protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration

struts2的请求参数url的写法以及相关struts2与Spring集成时的情况

在使用struts2的时候,我们都知道以前的那种以离散的值作为传递的单位,他们的请求url是这么写的: localhost:8080/test/login?username=hello&password=world 以及其对应的action是这么写的 <span style="font-size:24px;">public class LoginAction extends ActionSupport { private String username ; priv

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

在学习mybatis与spring整合是,想从外部引用一个db.properties数据库配置文件,在配置文件中使用占位符进行引用,如下: 1 <context:property-placeholder location="classpath:db.properties" /> 2 <bean id="dataSource" 3 class="org.springframework.jdbc.datasource.DriverManage

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

正常来说按照Spring官方配置,在struts2与spring整合时,struts配置文件中class属性指向spring配置的bean id,但是在class指向类路径时,依然能注入service. public class LoginAction extends ActionSupport{ private LoginService loginService; public void setLoginService(LoginService loginService) { System.o

MyBatis与Spring集成

beans.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.o