Mybatis源码(十一):Mybatis与Spring的整合
一、搭建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&characterEncoding=utf-8&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类图结构如下:
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 或映射器所共享使用。
![](https://img2023.cnblogs.com/blog/1680081/202303/1680081-20230326141816521-2006694089.png)
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方法。
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容器
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和事务
|