本章通过分析 mybatis-spring-x.x.x.jar Jar 包中的源码,了解 MyBatis 是如何与 Spring 进行集成的。

Spring 配置文件

MyBatis 与 Spring 集成,在 Spring 配置文件中配置了数据源、SqlSessionFactory、自动扫描 MyBatis 中的 Mapper 接口、事务管理等,这部分内容都交由 Spring 管理。部分配置内容如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" > <!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
lazy-init="true" init-method="init" destroy-method="close">
<property name="url" value="${dataSource.url}" />
<property name="username" value="${dataSource.username}" />
<property name="password" value="${dataSource.password}" />
</bean> <!-- 配置SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml" />
<property name="mapperLocations" value="classpath*:rabbit/template/mapper/*.xml" />
</bean> <!-- 配置自动扫描所有Mapper接口 -->
<bean id="mapperScan" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="rabbit.template.mapper" />
</bean> <!--配置事务管理 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean> <!-- 激活CGLIB代理功能 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<tx:annotation-driven transaction-manager="transactionManager" /> ......
</beans>

从配置文件中可以看出,MyBatis 与 Spring 集成后,MyBatis 中的 SqlSessionFactory 对象是由 SqlSessionFactoryBean 创建的。

SqlSessionFactoryBean

SqlSessionFactoryBean 类作为我们分析源码的入口,该类实现了 InitializingBean 接口,在 Bean 初始化的时候,会执行 afterPropertiesSet() 方法,最后会调用 buildSqlSessionFactory() 方法创建 SqlSessionFactory。

SqlSessionFactoryBean.buildSqlSessionFactory() 方法的具体实现如下:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
// 创建Configuration对象
if (this.configuration != null) {
configuration = this.configuration;
// 省略配置相关代码
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
configuration = new Configuration();
// 省略配置相关代码
}
// 配置objectFactory
// 根据Spring配置文件,设置Configuration.objectWrapperFactory
// 根据Spring配置文件,设置Configuration.objectFactory
// 扫描typeAliasesPackage指定的包,并为其中的类注册别名
// 为typeAliases集合中指定的类注册别名
// 注册plugins集合中指定的插件
// 扫描typeHandlersPackage指定的包,并注册其中的TypeHandler
// 注册指定的typeHandlers
// 配置databaseIdProvider
// 配置缓存 // 调parse()方法解析配置文件
if (xmlConfigBuilder != null) {
xmlConfigBuilder.parse();
}
// 如果未配置transactionFactory,则默认使用SpringManagedTransactionFactory
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
// 设置environment
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
// 根据mapperLocations配置,处理映射配置文件以及相应的Mapper接口
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
} if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
// 调用SqlSessionFactoryBuilder.build()方法,创建SqlSessionFactory对象并返回
return this.sqlSessionFactoryBuilder.build(configuration);
}

SqlSessionFactoryBuilder.build() 方法返回的对象类型是 DefaultSqlSessionFactory。

SqlSessionFactoryBean 同时实现了 FactoryBean 接口,重写了 getObject() 方法,该方法返回 DefaultSqlSessionFactory 对象。

当配置文件中 <bean> 的 class 属性配置的实现类是 FactoryBean 时,通过 getBean() 方法返回的不是 FactoryBean 本身,而是 FactoryBean#getObject() 方法所返回的对象,相当于FactoryBean#getObject() 代理了 getBean() 方法。

SqlSessionFactoryBean#getObject() 方法的具体实现如下:

public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
} return this.sqlSessionFactory;
}

在《MyBatis 技术内幕》这本书的4.2.4章详细描述了 mybatis-spring Jar 包中的 SpringManagedTransaction、SqlSessionTemplate、SqlSessionDaoSupport、MapperFactoryBean、MapperScannerConfigurer 类的作用,有不明白的可以参考。

接下来继续介绍 Spring 配置文件中的 MapperScannerConfigurer Bean 对象。

MapperScannerConfigurer

MapperScannerConfigurer 类实现了 BeanDefinitionRegistryPostProcessor 接口,该接口中的 postProcessBeanDefinitionRegistry() 方法会在系统初始化的过程中被调用,该方法扫描了配置文件中配置的basePackage 下的所有 Mapper 类,最终生成 Spring 的 Bean 对象,注册到容器中。

postProcessBeanDefinitionRegistry() 方法具体实现如下:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 创建ClassPathMapperScanner对象
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
// 根据上面的配置,生成相应的过滤器。这些过滤器在扫描过程中会过滤掉不符合添加的内容,例如,
// annotationClass字段不为null时,则会添加AnnotationTypeFilter过滤器,通过该过滤器
// 实现只扫描annotationClass注解标识的接口的功能
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

ClassPathMapperScanner.scan() 方法会调用父类的 scan() 方法,核心逻辑在 doScan() 方法中实现,具体实现如下:

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类的doScan()方法,遍历basePackages中指定的所有包,扫描每个包下的Java文件并进行解析。
// 使用之前注册的过滤器进行过滤,得到符合条件的BeanDefinitionHolder对象
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
// 处理扫描得到的BeanDefinitionHolder集合
processBeanDefinitions(beanDefinitions);
} return beanDefinitions;
}

ClassPathMapperScanner.processBeanDefinitions() 方法会对 doScan() 方法中扫描到的 BeanDefinition 集合进行修改,主要是将其中记录的接口类型改造为 MapperFactoryBean 类型,并填充 MapperFactoryBean 所需的相关信息。

ClassPathMapperScanner.processBeanDefinitions() 方法具体实现如下:

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
} // 将扫描到的接口类型作为构造方法的参数
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
// 将BeanDefinition中记录的Bean类型修改为MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
// 构造MapperFactoryBean的属性,将sqlSessionFactory、sqlSessionTemplate
// 等信息填充到BeanDefinition中
// 修改自动注入方式
}
}

ClassPathMapperScanner 在处理 Mapper 接口的时候用到了 MapperFactoryBean 类,它是 MyBatis-Spring 提供的一个动态代理的实现,可以直接将 Mapper 接口注入到 Service 层的 Bean 中,这样就不需要编写任何 DAO 实现的代码。

MapperFactoryBean 的继承关系如图所示:

MapperFactoryBean 类的动态代理功能是通过实现了 Spring 提供的 FactoryBean 接口实现的,该接口是一个用于创建 Bean 对象的工厂接口,通过 geObject() 方法获取真实的对象。

MapperFactoryBean.geObject() 方法的实现如下:

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

通过 SqlSession 对象获取 Mapper 文件,《MyBatis 源码篇-SQL 执行的流程》介绍过 Mapper 接口的代理对象的获取过程。

getSqlSession() 是 SqlSessionDaoSupport 中的方法。该类的 sqlSession 属性是通过 Spring 容器自动注入 sqlSessionFactory 属性实现的。源码实现如下:

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}

这样就不难理解,在 Spring 容器中,Mapper 接口对应的实现类还是 MyBatis 提供的 MapperProxy 代理对象。MapperFactoryBean 类只不过是包装了一下,让真正的对象能够注入到 Spring 容器中。所以 Mapper 接口对应的实现类是作为单例一直存在 Spring 容器中的。

因为引入了 Spring 框架,增加了这块的源码的理解难度,下面通过一个简单的时序图总结 Mapper 实现类的生成过程。

SqlSessionTemplate

SqlSessionTemplate 实现了 SqlSession 接口,在 MyBatis 与 Spring 集成开发时,用来代替 MyBatis 中的 DefaultSqlSession 的功能。

SqlSessionTemplate 是线程安全的,可以在 DAO 之间共享使用,比如上面生成的 Mapper 对象会持有一个 SqlSessionTemplate 对象,每次请求都会共用该对象。在 MyBatis 中 SqlSession 的 Scope 是会话级别,请求结束后需要关闭本次会话,怎么集成了 Spring 后,可以共用了?

首先,在集成 Spring 后,Mapper 对象是单例,由 Spring 容器管理,供 Service 层使用,SqlSessionTemplate 在设计的时候,功能分成了如下两部分:

  1. 获取 MapperProxy 代理对象;
  2. 执行 SQL 操作,该部分功能通过代理对象 SqlSessionInterceptor 实现;

两类方法的具体实现如下:

@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
} @Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.<T> selectOne(statement);
}

getMapper 方法是可以共享的,SQL 操作的相关方法是会话级别的不能共享,所以在 SqlSessionTemplate 类中通过动态代理的方式来区分这两部分功能的实现。

接下来看看 SqlSessionTemplate 类中的动态代理部分的实现:

// SqlSession 的代理类
private final SqlSession sqlSessionProxy; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 创建SqlSession的代理类,给sqlSessionProxy属性赋值
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
// 代理类
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 通过SqlSessionUtils.getSqlSession()获取SqlSession对象,同一个事务共享SqlSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
// 调用SqlSession对象的相应方法
Object result = method.invoke(sqlSession, args);
// 检测事务是否由Spring进行管理,并据此决定是否提交事务
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
}
}

MyBatis 源码篇

MyBatis 源码篇-MyBatis-Spring 剖析的更多相关文章

  1. MyBatis 源码篇-Transaction

    本章简单介绍一下 MyBatis 的事务模块,这块内容比较简单,主要为后面介绍 mybatis-spring-1.**.jar(MyBatis 与 Spring 集成)中的事务模块做准备. 类图结构 ...

  2. MyBatis 源码篇-DataSource

    本章介绍 MyBatis 提供的数据源模块,为后面与 Spring 集成做铺垫,从以下三点出发: 描述 MyBatis 数据源模块的类图结构: MyBatis 是如何集成第三方数据源组件的: Pool ...

  3. MyBatis 源码篇-整体架构

    MyBatis 的整体架构分为三层, 分别是基础支持层.核心处理层和接口层,如下图所示. 基础支持层 反射模块 该模块对 Java 原生的反射进行了良好的封装,提供了更加简洁易用的 API ,方便上层 ...

  4. MyBatis 源码篇-插件模块

    本章主要描述 MyBatis 插件模块的原理,从以下两点出发: MyBatis 是如何加载插件配置的? MyBatis 是如何实现用户使用自定义拦截器对 SQL 语句执行过程中的某一点进行拦截的? 示 ...

  5. MyBatis 源码篇-日志模块2

    上一章的案例,配置日志级别为 debug,执行一个简单的查询操作,会将 JDBC 操作打印出来.本章通过 MyBatis 日志部分源码分析它是如何实现日志打印的. 在 MyBatis 的日志模块中有一 ...

  6. MyBatis 源码篇-日志模块1

    在 Java 开发中常用的日志框架有 Log4j.Log4j2.Apache Common Log.java.util.logging.slf4j 等,这些日志框架对外提供的接口各不相同.本章详细描述 ...

  7. MyBatis 源码篇-资源加载

    本章主要描述 MyBatis 资源加载模块中的 ClassLoaderWrapper 类和 Java 加载配置文件的三种方式. ClassLoaderWrapper 上一章的案例,使用 org.apa ...

  8. MyBatis 源码篇-SQL 执行的流程

    本章通过一个简单的例子,来了解 MyBatis 执行一条 SQL 语句的大致过程是怎样的. 案例代码如下所示: public class MybatisTest { @Test public void ...

  9. 深入浅出Mybatis系列(五)---TypeHandler简介及配置(mybatis源码篇)

    上篇文章<深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)>为大家介绍了mybatis中别名的使用,以及其源码.本篇将为大家介绍TypeH ...

随机推荐

  1. numpy模块-渐入佳境

    1.多维数组降为一维: numpy中的ravel().flatten().squeeze()的用法与区别 2. axis的理解 Python之NumPy(axis=0/1/2...)的透彻理解——通过 ...

  2. 梯度下降法(BGD & SGD & Mini-batch SGD)

    梯度下降法(Gradient Descent) 优化思想:用当前位置的负梯度方向作为搜索方向,亦即为当前位置下降最快的方向,也称“最速下降法”.越接近目标值时,步长越小,下降越慢. 如下图所示,梯度下 ...

  3. SQL-W3School-函数:SQL COUNT() 函数

    ylbtech-SQL-W3School-函数:SQL COUNT() 函数 1.返回顶部 1. COUNT() 函数返回匹配指定条件的行数. SQL COUNT() 语法 SQL COUNT(col ...

  4. Android架构(一)MVP架构在Android中的实践

    Android架构(一)MVP架构在Android中的实践 https://www.300168.com/yidong/show-2790.html   核心提示:为什么要重视程序的架构设计 对程序进 ...

  5. [Java复习] 多线程 并发 JUC 补充

    线程安全问题? 当多个线程共享同一个全局变量,做写的操作时,可能会受到其他线程的干扰.读不会发生线程安全问题. --  Java内存模型. 非静态同步方法使用什么锁? this锁 静态同步方法使用什么 ...

  6. SQL语言Select经典语句

    -- 示例数据 Select * From Employee Select * From Department -- 返回工资最高的员工的信息 Select * From Employee where ...

  7. Linux交换空间(swap space)的那些优缺点

    下面的所有例子都在ubuntu-server-x86_64 16.04下执行通过 什么是swap? swap space是磁盘上的一块区域,可以是一个分区,也可以是一个文件,或者是他们的组合.简单点说 ...

  8. mysql查看被锁住的表

    转: mysql查看被锁住的表 2019年05月14日 11:58:59 hlvy 阅读数 1068更多 分类专栏: mysql mysql   转:https://blog.51cto.com/mo ...

  9. 阶段5 3.微服务项目【学成在线】_day18 用户授权_16-细粒度授权-我的课程细粒度授权-测试

    重启课程管理服务 刷新页面,令牌到期需要先登陆 首先拿到company的id 测试2号公司

  10. 阶段5 3.微服务项目【学成在线】_day17 用户认证 Zuul_04-用户认证-认证服务查询数据库-查询用户接口-接口开发

    定义dao 权限放在授权的课程里面做,现在先不管.我们还需要查企业信息,就是用户所属的公司 公司表 对应关系在xc_company 这是一个关系 表 这个表里有唯一索引 user_id 所以根据use ...