Mybatis源码(十一):Mybatis与Spring的整合

发布时间 2023-03-26 14:37:06作者: 无虑的小猪

一、搭建mybtais-spring运行环境

1、创建数据表并初始化

CREATE TABLE `user` (
  `id` int(8) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(32) CHARACTER SET latin1 DEFAULT NULL COMMENT '名称',
  `create_date` datetime DEFAULT NULL COMMENT '创建时间',
  `update_date` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=667 DEFAULT CHARSET=utf8mb4;

INSERT INTO `user`(`id`, `name`, `create_date`, `update_date`) VALUES (101, 'zs', '2022-12-03 12:17:36', '2022-12-03 12:17:36');
INSERT INTO `user`(`id`, `name`, `create_date`, `update_date`) VALUES (102, 'ls', '2022-12-03 12:17:36', '2022-12-03 12:17:36');
INSERT INTO `user`(`id`, `name`, `create_date`, `update_date`) VALUES (103, 'ww', '2023-01-10 09:10:24', '2023-01-10 09:10:27');

2、导入pom.xml依赖

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.4</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.14</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.0</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.46</version>
</dependency>

3、新建实体User

 1 import java.util.Date;
 2 
 3 public class User {
 4 
 5     private Integer id;
 6 
 7     private String name;
 8 
 9     private Date createDate;
10 
11     private Date updateDate;
12 
13     public Integer getId() {
14         return id;
15     }
16 
17     public void setId(Integer id) {
18         this.id = id;
19     }
20 
21     public String getName() {
22         return name;
23     }
24 
25     public void setName(String name) {
26         this.name = name;
27     }
28 
29     public Date getCreateDate() {
30         return createDate;
31     }
32 
33     public void setCreateDate(Date createDate) {
34         this.createDate = createDate;
35     }
36 
37     public Date getUpdateDate() {
38         return updateDate;
39     }
40 
41     public void setUpdateDate(Date updateDate) {
42         this.updateDate = updateDate;
43     }
44 
45     @Override
46     public String toString() {
47         return "User{" +
48                 "id=" + id +
49                 ", name='" + name + '\'' +
50                 ", createDate=" + createDate +
51                 ", updateDate=" + updateDate +
52                 '}';
53     }
54 }

4、新建SpringUserMapper

  SpringUserMapper.java 接口:

public interface SpringUserMapper {
    List<User> selectUserList();
}

  SpringUserMapper.xml SQL映射文件:

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE mapper
 3         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 4         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5 <mapper namespace="com.snails.mapper.SpringUserMapper">
 6 
 7     <select id="selectUserList" resultType="com.snails.entity.User">
 8         select * from user
 9     </select>
10 
11 </mapper>

5、配置文件

  数据库连接配置文件,db.properties

1 jdbc.driver=com.mysql.jdbc.Driver
2 jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=GMT%2B8
3 jdbc.username=root
4 jdbc.password=root

  mybtais配置文件,mybatis-spring-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 开启驼峰模式 -->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

  Spring配置文件,applicationContext.xml

 1 <beans xmlns="http://www.springframework.org/schema/beans"
 2        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 3        xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
 4        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
 5       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
 6       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
 7       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
 8     <!-- 关联数据属性文件 -->
 9     <context:property-placeholder location="classpath:db.properties"/>
10     <!-- 开启扫描 -->
11     <context:component-scan base-package="com.snails"/>
12 
13     <!-- 配置数据源 -->
14     <bean class="com.alibaba.druid.pool.DruidDataSource"
15           id="dataSource" >
16         <property name="driverClassName" value="${jdbc.driver}"></property>
17         <property name="url" value="${jdbc.url}"></property>
18         <property name="username" value="${jdbc.username}"></property>
19         <property name="password" value="${jdbc.password}"></property>
20     </bean>
21     <!-- 整合mybatis -->
22     <bean class="org.mybatis.spring.SqlSessionFactoryBean"
23           id="sqlSessionFactoryBean" >
24         <!-- 关联数据源 -->
25         <property name="dataSource" ref="dataSource"/>
26         <!-- 关联mybatis的配置文件 -->
27         <property name="configLocation" value="classpath:mybatis-spring-config.xml"/>
28         <!-- 指定映射文件的位置 -->
29         <property name="mapperLocations" value="classpath:mapper/*.xml" />
30     </bean>
31     <!-- 配置扫描的路径 -->
32     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
33         <property name="basePackage" value="com.snails.mapper"/>
34     </bean>
35 
36 </beans>

6、单元测试

 1 import com.snails.entity.User;
 2 import com.snails.mapper.SpringUserMapper;
 3 import org.junit.Test;
 4 import org.junit.runner.RunWith;
 5 import org.springframework.beans.factory.annotation.Autowired;
 6 import org.springframework.test.context.ContextConfiguration;
 7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 8 import java.util.List;
 9 
10 @ContextConfiguration(locations = {"classpath:applicationContext.xml"})
11 @RunWith(value = SpringJUnit4ClassRunner.class)
12 public class MybatisSpringTest {
13 
14     @Autowired
15     private SpringUserMapper springUserMapper;
16 
17     @Test
18     public void testQuery(){
19         List<User> users = springUserMapper.selectUserList();
20         for (User user : users) {
21             System.out.println(user);
22         }
23     }
24 }

  执行结果如下:

            运行环境搭建完毕,在单元测试案例中将Mybatis整合到Spring中后,查询数据库并没有用到Mybtais中的核心对象SqlSessionFactory、SqlSession,下面来看看mybatis与spring是如何整合的。

二、官网解读

  Mybatis-Spring官网地址:http://mybatis.org/spring/zh/index.html

  MyBatis-Spring 将 MyBatis 代码无缝地整合到 Spring 中,将Mybatis的Mapper映射器、SqlSession会话对象的创建交由Spring管理,使Mybatis参与到Spring的事务管理中,并将 Mybatis 的异常转换为 Spring 的 DataAccessException。Mybtais-Spring根据Spring的特性,对Myabtis做了一层封装,在实际执行过程中依然会用到Myabtis的核心对象。

1、SqlSessionFactory的创建

  在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 创建。

2、事务

  MyBatis-Spring 允许 MyBatis 参与到 Spring 的事务管理中,并不会给 MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的 DataSourceTransactionManager 来实现事务管理。 配置好 Spring 的事务管理器,可以按 Spring 的方式来配置事务,支持 @Transactional 注解和 AOP 风格的配置。

  在事务处理期间,一个单独的 SqlSession 对象将会被创建和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。

3、sqlSession

  在 MyBatis 中,使用 SqlSessionFactory 来创建 SqlSession。 一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。

  SqlSessionTemplate 是 MyBatis-Spring 的核心,可以使用它替代 Mybatis中的 SqlSession。 SqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。

   当调用 SQL 方法时(包括由 getMapper() 方法返回的映射器中的方法),SqlSessionTemplate 将会保证使用的 SqlSession 与当前 Spring 的事务相关。 此外,它管理 session 的生命周期,包含必要的关闭、提交或回滚操作。另外,它也负责将 MyBatis 的异常翻译成 Spring 中的 DataAccessExceptions。

三、源码分析

  在进行正式的源码分析之前,先回顾一下使用Mybatis查询数据库的核心步骤:

1 // 创建sqlSessionFactory
2 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
3 // 创建sqlSession
4 SqlSession sqlSession = sqlSessionFactory.openSession();
5 // 创建Mapper映射器对象
6 UserMapper mapper = sqlSession.getMapper(UserMapper.class);

  在mybatis-spring中,是如何处理这三个核心对象的呢?从Spring的配置文件applicationContext.xml入手,看看Spring整合Mybtais的配置内容:

<!-- 整合mybatis -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean" >
    <!-- 关联数据源 -->
    <property name="dataSource" ref="dataSource"/>
    <!-- 关联mybatis的配置文件 -->
    <property name="configLocation" value="classpath:mybatis-spring-config.xml"/>
    <!-- 指定映射文件的位置 -->
    <property name="mapperLocations" value="classpath:mapper/*.xml" />
</bean>

1、SqlSessionFactory的创建

  官网描述:在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。        SqlSessionFactoryBean类图结构如下:

0

  SqlSessionFactoryBean 实现了 Spring 的 FactoryBean 接口,由 Spring 最终创建的 bean 并不是 SqlSessionFactoryBean 本身,而是工厂类(SqlSessionFactoryBean)的 getObject() 方法的返回结果,Spring 将会在应用启动时创建 SqlSessionFactory,并使用 sqlSessionFactory 作为beanName存储到IOC容器中。

  在Spring生命周期中这三个接口的作用:

接口
方法
作用
FactoryBean
getObject()
返回由FactoryBean创建的Bean实例
InitializingBean
afterPropertiesSet()
bean初始化完成后属性设置操作
ApplicationListener
onApplicationEvent()
ApplicationConext事件监听

  在初始化SqlSessionFactoryBean时,调用初始化方法invokeInitMethods。

  SqlSessionFactoryBean#afterPropertiesSet() 核心伪代码:

public void afterPropertiesSet() throws Exception {
  //...
  // 构建sqlSessionFactory
  this.sqlSessionFactory = buildSqlSessionFactory();
}

  SqlSessionFactoryBean#buildSqlSessionFactory() 中主要完成以下几件事:

 1 protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
 2   // ... configuration全局对象的属性设置
 3   // 1、mybtais全局配置文件的解析
 4   if (xmlConfigBuilder != null) {
 5       xmlConfigBuilder.parse();
 6   }
 7   // ...
 8   // 2、Mapper映射文件的解析
 9   for (Resource mapperLocation : this.mapperLocations) {
10       XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
11           targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
12       xmlMapperBuilder.parse();
13   }
14   // ..
15   // 构建DefaultSqlSessionFactory对象
16   return this.sqlSessionFactoryBuilder.build(targetConfiguration);
17 }

1、buildSqlSessionFactory()

  1.1、configuration全局对象的属性设置,若有mybatis的全局配置文件,解析mybtais的全局配置文件

  1.2、Mapper映射文件的解析

  1.3、创建SqlSessionFactory对象

2、getObect()

  SqlSessionFactoryBean实现了FactoryBean接口,在FactoryBean简化了bean实例的获取流程,允许用户自定义实例化bean逻辑。获取bean实例,实际上是获取FactoryBean的getObject()返回的对象。SqlSessionFactoryBean#getObect() 核心代码:

1 public SqlSessionFactory getObject() throws Exception {
2   if (this.sqlSessionFactory == null) {
3     afterPropertiesSet();
4   }
5   
6   return this.sqlSessionFactory;
7 }

  返回SqlSessionFactory对象,若SqlSessionFactory对象为空,就调用afterPropertiesSet方法来完成配置文件的解析和SqlSessionFactory的创建。

3、onApplicationEvent()

  在Spring容器加载完成后,监控MappedStatement是否加载完毕。

1 public void onApplicationEvent(ApplicationEvent event) {
2   if (failFast && event instanceof ContextRefreshedEvent) {
3     // fail-fast -> check all statements are completed
4     this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
5   }
6 }

4、小结

  Mybatis-Spring通过定义一个实现了InitializingBean接口的SqlSessionFactoryBean类,在SqlSessionFactoryBean属性值设置完成时调用afterPropertiesSet()方法,完成了SqlSessionFactory对象的创建、相关配置文件和映射文件的解析操作。也就是说,Spring在启动初始化这个Bean的时候,完成了解析和工厂类的创建工作。

2、SqlSession的创建

  在 MyBatis 中,使用 SqlSessionFactory 来创建 DefaultSqlSession。 使用 SqlSession 来执行映射了的语句,提交或回滚连接,执行完成关闭 session。DefaultSqlSession是线程不安全的,无法直接在Spring中应用。

 

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

1、SqlSessionTemplate
 

   SqlSessionTemplate是被Spring管理的,线程安全的,被所有DAOS共享的单例对象。在SqlSessionTemplate中关于数据的增删改查操作都通过代理属性sqlSessionProxy完成的。

   

  sqlSessionProxy在SqlSessionTemplate的构造函数中完成初始化,构造函数详情如下:

 1 public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
 2     PersistenceExceptionTranslator exceptionTranslator) {
 3 
 4   this.sqlSessionFactory = sqlSessionFactory;
 5   this.executorType = executorType;
 6   this.exceptionTranslator = exceptionTranslator;
 7   // 通过JDK动态代理,创建了一个SqlSession接口的代理对象,调用SqlSessionProxy中的方法,实际上是调用SqlSessionInterceptor的invoke方法
 8   this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
 9       new Class[] { SqlSession.class }, new SqlSessionInterceptor());
10 }

  sqlSessionProxy是代理对象,SqlSessionTemplate的select、insert、update、delete等操作是通过调用sqlSessionProxy的方法完成的,实际上是调用SqlSessionInterceptor的invoke方法。

  SqlSessionInterceptor#invoke() 核心代码:
 1 private class SqlSessionInterceptor implements InvocationHandler {
 2   @Override
 3   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 4     // 1、获取DefaultSqlSession对象(内含openSession的操作)
 5     SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
 6         SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
 7     try {
 8       // 2、调用DefaultSqlSession的select、insert、update、delete方法(mybatis流程)
 9       Object result = method.invoke(sqlSession, args);
10       // 3、涉及事务,在关闭session前,提交事务
11       if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
12         sqlSession.commit(true);
13       }
14       return result;
15     } catch (Throwable t) {
16       // 4、统一抛出异常处理
17       Throwable unwrapped = unwrapThrowable(t);
18       if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
19         // 释放连接,避免死锁
20         closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
21         sqlSession = null;
22         Throwable translated = SqlSessionTemplate.this.exceptionTranslator
23             .translateExceptionIfPossible((PersistenceException) unwrapped);
24         if (translated != null) {
25           unwrapped = translated;
26         }
27       }
28       throw unwrapped;
29     } finally {
30       // 释放连接,关闭DefaultSqlSession
31       if (sqlSession != null) {
32         closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
33       }
34     }
35   }
36 }

1、获取DefaultSqlSession对象

  SqlSessionInterceptor#getSqlSession() 核心伪代码:

1 public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
2     PersistenceExceptionTranslator exceptionTranslator) {
3   // ...
4   // 获取DefaultSqlSession对象
5   session = sessionFactory.openSession(executorType);
6   // ...
7   return session;
8 }

  DefaultSqlSessionFactory#openSessionFromDataSource() 核心代码:

 1 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
 2   Transaction tx = null;
 3   try {
 4     final Environment environment = configuration.getEnvironment();
 5     final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
 6     tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
 7     final Executor executor = configuration.newExecutor(tx, execType);
 8     // 创建DefaultSqlSession对象
 9     return new DefaultSqlSession(configuration, executor, autoCommit);
10   } catch (Exception e) {
11     // 关闭事务
12     closeTransaction(tx);
13     throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
14   } finally {
15     ErrorContext.instance().reset();
16   }
17 }

2、调用DefaultSqlSession的操作数据库方法

3、提交事务

4、统一异常处理

5、释放连接,关闭DefaultSqlSession

6、SqlSessionDaoSupport封装SqlSessionTemplate

  在实际的开发过程中,基本没有设置到SqlSessionTemplate,因为SqlSessionTemplate作为SqlSessionDaoSupport的属性,通过SqlSessionTemplate的setSqlSessionFactory方法,完成sqlSessionTemplate属性的初始化。

SqlSessionDaoSupport使用示例:

配置文件不变:
 1 import org.apache.ibatis.session.SqlSessionFactory;
 2 import org.mybatis.spring.support.SqlSessionDaoSupport;
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 
 5 public class BaseDao extends SqlSessionDaoSupport {
 6 
 7     @Autowired
 8     private SqlSessionFactory sqlSessionFactory;
 9 
10     // 初始化SqlSessionTemplate,@Autowired 可以修饰属性,构造方法,set方法,默认依据类型(属性类型,参数类型)为属性注入值
11     @Autowired
12     public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
13         super.setSqlSessionFactory(sqlSessionFactory);
14     }
15 
16     public Object selectList(String statement, Object... parameter) {
17         return getSqlSession().selectList(statement, parameter);
18     }
19 }
 1 package com.snails.dao;
 2 
 3 import com.snails.entity.User;
 4 import com.snails.mapper.SpringUserMapper;
 5 import org.springframework.stereotype.Repository;
 6 import java.util.List;
 7 
 8 @Repository
 9 public class UserDaoImpl extends BaseDao implements SpringUserMapper {
10 
11     @Override
12     public List<User> selectUserList() {
13         return (List<User>) selectList("com.snails.mapper.SpringUserMapper.selectUserList");
14     }
15 }
 1 import com.snails.dao.UserDaoImpl;
 2 import com.snails.entity.User;
 3 import com.snails.mapper.SpringUserMapper;
 4 import org.junit.Test;
 5 import org.junit.runner.RunWith;
 6 import org.mybatis.spring.support.SqlSessionDaoSupport;
 7 import org.springframework.beans.factory.annotation.Autowired;
 8 import org.springframework.test.context.ContextConfiguration;
 9 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10 import java.util.List;
11 
12 @ContextConfiguration(locations = {"classpath:applicationContext.xml"})
13 @RunWith(value = SpringJUnit4ClassRunner.class)
14 public class MybatisSpringTest {
15 
16     @Autowired
17     private UserDaoImpl userDao;
18 
19     @Test
20     public void testSqlSessionDaoSupport(){
21         List<User> users = userDao.selectUserList();
22         for (User user : users) {
23             System.out.println(user);
24         }
25     }
26 }

3、Mapper接口代理对象的处理

  上述已经对SqlSessionFactory及SqlSession对象的创建做了分析。下面我们来看看Mapper接口代理对象是如何创建的。在配置文件applicationContext.xml中,完成Mapper映射接口扫描的配置如下:

<!-- 配置扫描的路径 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
    <property name="basePackage" value="com.snails.mapper"/>
</bean>

  MapperScannerConfigurer的类图接口如下:

  MapperScannerConfigurer实现了BeanFactoryPostRegistoryProcessor,BeanFactoryPostRegistoryProcessor是BFPP的子类。BFPP能能够在Spring创建Bean之前修改IOC容器中的beanDefinition,Spring创建Bean之前会调用postProcessBeanDefinitionRegistry()方法。

1、Mapper接口对象替换为MapperFactoryBean注入到IOC容器

  MapperScannerConfigurer#postProcessBeanDefinitionRegistry() 核心代码:
 1 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
 2   // 处理占位符
 3   if (this.processPropertyPlaceHolders) {
 4     processPropertyPlaceHolders();
 5   }
 6   
 7   // 创建 ClassPathMapperScanner 对象,用于扫描Mapper接口
 8   ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
 9   scanner.setAddToConfig(this.addToConfig);
10   scanner.setAnnotationClass(this.annotationClass);
11   scanner.setMarkerInterface(this.markerInterface);
12   scanner.setSqlSessionFactory(this.sqlSessionFactory);
13   scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
14   scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
15   scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
16   scanner.setResourceLoader(this.applicationContext);
17   scanner.setBeanNameGenerator(this.nameGenerator);
18   scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
19   if (StringUtils.hasText(lazyInitialization)) {
20     scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
21   }
22   scanner.registerFilters();
23   // 扫描basePackage字段中指定的包及其子包
24   scanner.scan(
25       StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
26 }

  最终会调用 ClassPathMapperScanner的doScan()方法,ClassPathMapperScanner#doScan() 核心代码:

 1 public Set<BeanDefinitionHolder> doScan(String... basePackages) {
 2   // 调用父类ClassPathBeanDefinitionScanner的doScan()方法,扫描basePackages中所有接口,并将接口添加到beanDefinitions中
 3   Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
 4 
 5   if (beanDefinitions.isEmpty()) {
 6     LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
 7         + "' package. Please check your configuration.");
 8   } else {
 9     // beanDefinition后置处理,主要将beanDefinition的beanClass属性修改为MapperFactoryBean
10     processBeanDefinitions(beanDefinitions);
11   }
12 
13   return beanDefinitions;
14 }

1、扫描指定包下的所有接口,并添加到beanDefinition中

2、beanDefinition的后置处理,替换beanDefinition的beanClass属性

  接口是没法创建实例对象的,所以在创建对象之前接口类型指向了一个具体的普通Java类MapperFactoryBean 。也就是说,所有的Mapper接口,在容器里面都被注册成一个支持泛型的MapperFactoryBean,然后在创建接口的对象时创建的就是MapperFactoryBean对象。

2、MapperFactoryBean返回代理对象

  为什么所有的Mapper接口要被注册成MapperFactoryBean?先来看看MapperFactoryBean的类图结构:

  MapperFactoryBean继承了SqlSessionDaoSupport,也就是每一个Mapper都可以拿到SqlSessionDaoSupport中的SqlSessionTemplate属性。

  MapperFactoryBean实现了FactoryBean接口,在向容器中注入MapperFactoryBean对象是,实际上是将getObject方法返回的对象注入容器中。

  MapperFactoryBean#getObject() 核心代码:

public T getObject() throws Exception {
  return getSqlSession().getMapper(this.mapperInterface);
}

  getObject方法中,先执行父类SqlSessionDaoSupport的getSqlSession()方法获取SqlSessionTemplate对象,通过SqlSessionTemplate的getMapper()方法,最终调用Configuration的getMapper方法,获取代理对象。

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

  MapperRegistry#getMapper() 核心代码:

  mapperroxyFactory@newInstance() 核心代码:

  在调用Mapper接口的任何方法,本质上是执行MapperProxy的invoke()方法,后续流程与myabtis编程式流程一样。

4、核心流程图

5、总结

  Spring整合Mybatis,Mybatis的核心对象在Spring启动过程中被创建。

1、SqlSessionFactory对象的创建

  在Spring创建SqlSessionFactoryBean的bean实例时完成配置文件的加载及SqlSessionFactory的创建。

2、SqlSession对象的创建

  在Spring中提供了SqlSessionTemplate,取代SqlSession。SqlSessionTemplate持有一个代理对象sqlSession,用该对象执行实际的操作数据库的方法,在执行对应方法时,实际上是调用SqlSessionInterceptor中的invoke方法。

  在SqlSessionInterceptor的invoke方法中获取DefaultSqlSession,这就是为什么SqlSessionTemplate是线程安全的原因。每次查询,都会创建DefaultSqlSession对象,查询结束后关闭。也就是说,每个针对数据库的请求都会创建DefaultSqlSession对象,线程私有。

  Spring中提供了抽象类SqlSessionDaoSupport,持有SqlSessionTemplate对象。

 Spring在创建Mapper接口对象MapperFactoryBean时,完成对SqlSessionTemplate的创建。因为MapperFactoryBean是SqlSessionDaoSupport的子类,SqlSessionTemplate是在SqlSessionDaoSupport构造函数中创建的。  

  SqlSessionTemplate还可以通过上述SqlSessionDaoSupport使用示例创建,在Spring创建UserDaoImpl实例时,通过set方法注入。

3、Mapper接口对象的创建

  扫描Mapper接口,注册到容器中的是MapperFactoryBean,它继承了SqlSessionDaoSupport,可以获得SqlSessionTemplate。

  MapperFactoryBean实现了FactoryBean接口,注入容器中的是getObject方法返回的对象,实际上是调用了SqlSessionTemplate的getMapper()方法,执行MapperProxyFactory的newInstance方法,生成了了一个JDK动态代理对象注入到IOC容器中。

4、执行MapperProxy的SQL处理流程

  执行Mapper接口的任意方法,会走到触发管理类MapperProxy,进入SQL处理流程。

5、核心对象

对象
生命周期
SqlSessionTemplate
Spring中SqlSession的替代品,线程安全
SqlSessionDaoSupport
用于获取SqlSessionTemplate
SqlSessionInterceptor(内部类)
代理对象,用来代理DefaultSqlSession,在SqlSessionTemplate中使用
MapperFactoryBean
代理对象,继承了SqlSessionDaoSupport用来获取SqlSessionTemplate
SqlSessionHolder
控制SqlSession和事务