Spring事务源码分析专题(二)Mybatis的使用及跟Spring整合原理分析

前言

专题要点以下:java

Spring事务源码分析专题(二)Mybatis的使用及跟Spring整合原理分析

本文要解决的是第二点,Mybatis的使用、原理及跟Spring整合原理分析。mysql

Mybatis的简单使用

搭建项目程序员

  1. pom文件添加以下依赖
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.15</version>
</dependency>
  1. 建立mybaits配置文件,mybatis-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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="password" value="123"/>
                <property name="username" value="root"/>
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/userMapper.xml"/>
    </mappers>
</configuration>
  1. 建立mapper.xml文件以下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
        "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.dmz.mapper.UserMapper">
    <select id="selectOne" resultType="org.apache.ibatis.dmz.entity.User">
        select * from user where id = #{id}
    </select>
</mapper>
  1. 实体类以下
public class User {

    private  int id;

    private String name;

    private int age;

    // 省略getter/setter方法

    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", age=" + age +
            '}';
    }
}
  1. 测试代码以下
    public class Main {
    public static void main(String[] args) throws Exception {
    String resource = "mybatis-config.xml";
    InputStream resourceAsStream = Resources.getResourceAsStream(resource);
    // 1.解析XML配置
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    // 2.基于解析好的XML配置建立一个SqlSessionFactory
    SqlSessionFactory sqlSessionFactory = builder.build(resourceAsStream);
    // 3.经过SqlSessionFactory,建立一个SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 4.测试直接调用mapper.xml中的方法
    Object o = sqlSession.selectOne("org.apache.ibatis.dmz.mapper.UserMapper.selectOne",2);
    if(o instanceof User){
      System.out.println("直接执行mapper文件中的sql查询结果:"+o);
    }
    // 5.获取一个代理对象
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    // 6.调用代理对象的方法
    System.out.println("代理对象查询结果:"+mapper.selectOne(1));
    }
    }

// 程序输出以下,分别对应了我本地数据库中的两条记录
// 直接执行mapper文件中的sql查询结果:User{id=2, name='dmz', age=18}
// 代理对象查询结果:User{id=1, name='dmz', age=18}spring

> **原理分析**

由于本专栏不是对mybatis的源码分析专题(笔者对于三大框架都会作一个源码分析专题),因此对这块的原理分析不会牵涉到过多源码级别的内容。

从上面的例子中咱们能够看到,对于Mybatis的使用主要有两种形式

1. 直接经过sqlsession调用相关的增删改查的API,例如在咱们上面的例子中就直接调用了sqlsession的selectOne方法完成了查询。使用这种方法咱们须要传入namespace+statamentId以便于Mybatis定位到要执行的SQL,另外还须要传入查询的参数
1. 第二种形式,则是先经过sqlsession建立一个代理对象,而后调用代理对象的方法完成查询

本文要探究的原理主要是第二种形式的使用,换而言之,就是Mybatis是如何生成这个代理对象的。在思考Mybatis是如何作的以前,咱们不妨想想,若是是咱们本身要实现这个功能,那么你会怎么去作呢?

若是是个人话,我会这么作:

![](https://s4.51cto.com/images/blog/202008/10/5d09bd2be5dcfffe5fb9ca6d0f34a152.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

固然我这种作法省略了不少细节,好比如何将方法参数绑定到SQL,如何封装结果集,是否对一样的Sql进行缓存等等。正常Mybatis在执行Sql时起码须要通过下面几个流程

![](https://s4.51cto.com/images/blog/202008/10/6992f5b6c7bd628daad4ad18624dc491.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
9

其中,Executor负责维护缓存以及事务的管理,它会将对数据库的相关操做委托给StatementHandler完成,StatementHandler会先经过ParameterHandler完成对Sql语句的参数的绑定,而后调用JDBC相关的API去执行Sql获得结果集,最后经过ResultHandler完成对结果集的封装。

本文只是对这个流程有个大体的了解便可,详细的流程介绍咱们在Mybatis的源码分析专栏中再聊~

# Mybaits中的事务管理

Mybatis中的事务管理主要有两种方式

1. 使用JDBC的事务管理机制:即利用JDBC中的java.sql.Connection对象完成对事务的提交(commit())、回滚(rollback())、关闭(close())等

1. 使用MANAGED的事务管理机制:这种机制MyBatis自身不会去实现事务管理,而是让程序的容器如(tomcat,jboss)来实现对事务的管理

在文章开头的例子中,我在mybatis-config.xml配置了

<transactionManager type="JDBC"/>sql

这意味着咱们选用了JDBC的事务管理机制,那么咱们在哪里能够开启事务呢?实际上Mybatis默认是关闭自动提交的,也就是说事务默认就是开启的。而是否开启事务咱们能够在建立SqlSession时进行控制。SqlSessionFactory提供了如下几个用于建立SqlSession的方法

SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)数据库

咱们在以为使用哪一个方法来建立SqlSession主要是根据如下几点

1. 是否要关闭自动提交,意味着开启事务
1. 使用外部传入的链接对象仍是从配置信息中获取到的链接对象
1. 使用哪一种执行方式,一共有三种执行方式
* ExecutorType.SIMPLE:每次执行SQL时都建立一个新的PreparedStatement
* ExecutorType.REUSE:复用PreparedStatement对象
* ExecutorType.BATCH:进行批处理
在前面的例子中,咱们使用的是空参的方法来建立SqlSession对象的,这种状况下Mybatis会建立一个开启了事务的、从配置的链接池中获取链接的、事务隔离级别跟数据库保持一致的、执行方式为ExecutorType.SIMPLE的SqlSession对象。

咱们基于上面的例子来体会一下Mybatis中的事务管理,代码以下:

public class Main {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
// 1.解析XML配置
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 2.基于解析好的XML配置建立一个SqlSessionFactory
SqlSessionFactory sqlSessionFactory = builder.build(resourceAsStream);
// 3.开启一个SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4.获取一个代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user =new User();
user.setId(3);
user.setName("dmz111");
user.setAge(27);
// 插入一条数据
mapper.insert(user);
// 抛出一个异常
throw new RuntimeException("发生异常!");
}
}apache

运行上面的代码,咱们会发现数据库中并不会新增一条数据,可是若是咱们在建立SqlSession时使用下面这种方式

 SqlSession sqlSession = sqlSessionFactory.openSession(true);
即便发生了异常,数据仍然会插入到数据库中

# Spring整合Mybatis的原理

首先明白一点,虽然我在以前介绍了Mybatis的事务管理,可是当Mybatis跟Spring进行整合时,事务的管理彻底由Spring进行控制!因此对于整合原理的分析不会涉及到事务的管理

咱们先来看一个Spring整合Mybatis的案例,我这里以JavaConfig的形式进行整合,核心配置以下:

@Configurationbr/>@ComponentScan("com.dmz.mybatis.spring")
// 扫描全部的mapper接口br/>@MapperScan("com.dmz.mybatis.spring.mapper")
public class MybatisConfig {缓存

@Bean
public DataSource dataSource() {
    DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
    driverManagerDataSource.setPassword("123");
    driverManagerDataSource.setUsername("root");
    driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
    driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8");
    return driverManagerDataSource;
}

// 须要配置这个SqlSessionFactoryBean来获得一个SqlSessionFactory
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean() throws Exception {
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dataSource());
    PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
    sqlSessionFactoryBean.setMapperLocations(patternResolver.getResources("classpath:mapper/*.xml"));
    return sqlSessionFactoryBean;
}

// 使用Spring中的DataSourceTransactionManager管理事务
@Bean
public TransactionManager transactionManager() {
    DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
    dataSourceTransactionManager.setDataSource(dataSource());
    return dataSourceTransactionManager;
}

}tomcat

从这段配置中咱们能够提炼出一个关键信息,若是咱们要弄清楚Spring是如何整合Mybatis的,咱们应该要弄明白两点

1. @MapperScan这个注解干了什么?
1. SqlSessionFactoryBean这个Bean的建立过程当中干了什么?

接下来咱们就分为两点来进行讨论

> **SqlSessionFactoryBean的初始化流程**

首先咱们看看这个类的继承关系

**继承关系**

![](https://s4.51cto.com/images/blog/202008/10/ccfd24a2e47157e095315f0c6fa28581.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

**源码分析**

看到它实现了InitializingBean接口,那咱们第一反应确定是查看下它的afterPropertiesSet方法,其源码以下:

public void afterPropertiesSet() throws Exception {
// 调用buildSqlSessionFactory方法完成对成员属性sqlSessionFactory的赋值
this.sqlSessionFactory = buildSqlSessionFactory();
}session

// 经过咱们在配置中指定的信息构建一个SqlSessionFactory
// 若是你对mybatis的源码有必定了解的话
// 这个方法作的事情实际就是先构造一个Configuration对象
// 这个Configuration对象表明了全部的配置信息
// 等价于咱们经过myabtis-config.xml指定的配置信息
// 而后调用sqlSessionFactoryBuilder的build方法建立一个SqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

final Configuration targetConfiguration;

// 接下来是经过配置信息构建Configuration对象的过程
// 我这里只保留几个重要的节点信息
XMLConfigBuilder xmlConfigBuilder = null;

// 咱们能够经过configLocation直接指定mybatis-config.xml的位置
if (this.configuration != null) {
    targetConfiguration = this.configuration;
    if (targetConfiguration.getVariables() == null) {
        targetConfiguration.setVariables(this.configurationProperties);
    } else if (this.configurationProperties != null) {
        targetConfiguration.getVariables().putAll(this.configurationProperties);
    }
} else if (this.configLocation != null) {
    xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
    LOGGER.debug(
        () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
    targetConfiguration = new Configuration();
    Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}

// 能够指定别名
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}

if (!isEmpty(this.typeAliases)) {
    Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
    });
}

// 这里比较重要,注意在这里将事务交由了Spring进行管理
targetConfiguration.setEnvironment(new Environment(this.environment,
                                                   this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
                                                   this.dataSource));

// 能够直接指定mapper.xml
if (this.mapperLocations != null) {
    if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
    } else {
        for (Resource mapperLocation : this.mapperLocations) {
            if (mapperLocation == null) {
                continue;
            }
            try {
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                                                                         targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                xmlMapperBuilder.parse();
            } catch (Exception e) {
                throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
            } finally {
                ErrorContext.instance().reset();
            }
            LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
    }
} else {
    LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}

return this.sqlSessionFactoryBuilder.build(targetConfiguration);

}

能够看到在初始化阶段作的最重要的是就是给成员变量sqlSessionFactory赋值,同时咱们知道这是一个FactoryBean,那么不出意外,它的getObject能够是返回了这个被赋值的成员变量,其源码以下:

public SqlSessionFactory getObject() throws Exception {
// 初始化阶段已经赋值了
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
// 果不其然,直接返回
return this.sqlSessionFactory;
}

> **@MapperScan工做原理**

查看@MapperScan这个注解的源码咱们会发现

@Retention(RetentionPolicy.RUNTIME)br/>@Target(ElementType.TYPE)
@Documentedbr/>@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

// basePackages属性的别名,等价于basePackages
String[] value() default {};

// 扫描的包名
String[] basePackages() default {};

// 能够提供一个类,以类的包名做为扫描的包
Class<?>[] basePackageClasses() default {};

// BeanName的生成器,通常用默认的就好啦
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

// 指定要扫描的注解
Class<? extends Annotation> annotationClass() default Annotation.class;

// 指定标记接口,只有继承了这个接口才会被扫描
Class<?> markerInterface() default Class.class;

// 指定SqlSessionTemplate的名称,
// SqlSessionTemplate是Spring对Mybatis中SqlSession的封装
String sqlSessionTemplateRef() default "";

// 指定SqlSessionFactory的名称
String sqlSessionFactoryRef() default "";

// 这个属性是什么意思呢?Spring跟Mybatis整合
// 最重要的事情就是将Mybatis生成的代理对象交由Spring来管理
// 实现这个功能的就是这个MapperFactoryBean
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

// 是否对mapper进行懒加载,默认为false
String lazyInitialization() default "";

}

接着咱们就来看看MapperScannerRegistrar作了什么,其源码以下:

// 这里咱们只关注它的两个核心方法
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取到@MapperScan这个注解中的属性
AnnotchaationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
// 紧接着开始向Spring容器中注册bd
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}

void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {

// 打算注册到容器中的bd的beanClass属性为MapperScannerConfigurer.class
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);

// 省略部分代码
// ....
// 这部分代码就是将注解中的属性获取出来
// 放到MapperScannerConfigurer这个beanDefinition中

// 最后将这个beanDefinition注册到容器中
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}

到这里咱们能够肯定了,@MapperScan这个注解最大的做用就是向容器中注册一个MapperScannerConfigurer,咱们顺藤摸瓜,再来分析下MapperScannerConfigurer是用来干吗的

MapperScannerConfigurer分析

**继承关系**

![](https://s4.51cto.com/images/blog/202008/10/56e949bc31c51a957d019844a52b3f3c.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
image-20200722092411193
从上面这张图中咱们能得出的一个最重要的信息就是,MapperScannerConfigurer是一个Bean工厂的后置处理器,而且它实现的是BeanDefinitionRegistryPostProcessor,而BeanDefinitionRegistryPostProcessor一般都是用来完成扫描的,咱们直接定位到它的postProcessBeanDefinitionRegistry方法,源码以下:

**方法分析**

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
// 处理@MaperScan注解属性中的占位符
processPropertyPlaceHolders();
}
// 在这里建立了一个ClassPathMapperScanner
// 这个类继承了ClassPathBeanDefinitionScanner,并复写了它的doScan、registerFilters等方法
// 其总体行为跟ClassPathBeanDefinitionScanner差很少,
// 关于ClassPathBeanDefinitionScanner的分析能够参考以前的《你知道Spring是怎么解析配置类的吗?》
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);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
// 这里设置了扫描规则
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

这个方法的总体实现逻辑仍是比较简单的,内部就是建立了一个ClassPathMapperScanner来进行扫描,这个类自己继承自ClassPathBeanDefinitionScanner,关于ClassPathBeanDefinitionScanner在以前的文章中已经作过详细分析了,见《你知道Spring是怎么解析配置类的吗?》若是你没有看过以前的文章,问题也不大,你只须要知道是这个类完成了扫描并将扫描获得的BeanDefinition注册到容器中便可。ClassPathMapperScanner复写了这个类的doScan方法已经registerFilters,而在doScan方法中这个类只是简单调用了父类的doScan方法完成扫描在对扫描后获得的BeanDefinition作一些后置处理,也就是说ClassPathMapperScanner只是在父类的基础上定义了本身的扫描规则,经过对扫描后的BeanDefinition会作进一步的处理。

基于此,咱们先来看看,它的扫描规则是怎么样的?查看其registerFilters及isCandidateComponent方法,代码以下:

// 这个方法的代码仍是很简单的
public void registerFilters() {
boolean acceptAllInterfaces = true;

// 第一步,判断是否要扫描指定的注解
// 也就是判断在@MapperScan注解中是否指定了要扫描的注解
if (this.annotationClass != null) {
    addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
    acceptAllInterfaces = false;
}

// 第二步,判断是否要扫描指定的接口
// 一样也是根据@MapperScan注解中的属性作判断
if (this.markerInterface != null) {
    addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
            return false;
        }
    });
    acceptAllInterfaces = false;
}

// 若是既没有指定注解也没有指定标记接口
// 那么全部.class文件都会被扫描
if (acceptAllInterfaces) {
    addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}

// 排除package-info文件
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
    String className = metadataReader.getClassMetadata().getClassName();
    return className.endsWith("package-info");
});

}

// 这个方法会对扫描出来的BeanDefinition进行检查,必须符合要求才会注册到容器中
// 从这里咱们能够看出,BeanDefinition必需要是接口才行
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}

从上面两个方法中咱们能够得出结论,默认状况下@MapperScan注解会扫描指定包下的全部接口。

在前文咱们也提到了,ClassPathBeanDefinitionScanner不只自定义了扫描的规则,并且复写了doScan方法,在完成扫描后会针对扫描出来的BeanDefinition作一下后置处理,那么它作了什么呢?咱们查看它的processBeanDefinitions方法,其源码以下:

// 下面这个方法看起来代码很长,实际作的事情确很简单
// 主要作了这么几件事
// 1.将扫描出来的BeanDefinition的beanClass属性设置为MapperFactoryBeanClass.class
// 2.在BeanDefinition的ConstructorArgumentValues添加一个参数
// 限定实例化时使用MapperFactoryBeanClass的带参构造函数
// 3.检查是否显示的配置了sqlSessionFactory或者sqlSessionTemplate
// 4.若是没有进行显示配置,那么将这个BeanDefinition的注入模型设置为自动注入
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();

// 往构造函数的参数集合中添加了一个值,那么在实例化时就会使用带参的构造函数
    // 等价于在XML中配置了
    // <constructor-arg name="mapperInterface" value="mapperFactoryBeanClass"/>
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 

    // 将真实的BeanClass属性设置为mapperFactoryBeanClass
    definition.setBeanClass(this.mapperFactoryBeanClass);

    definition.getPropertyValues().add("addToConfig", this.addToConfig);

    // 开始检查是否显示的指定了sqlSessionFactory或者sqlSessionTemplate
    boolean explicitFactoryUsed = false;

    // 首先检查是否在@MapperScan注解上配置了sqlSessionFactoryRef属性
    if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {

        // 若是配置了的话,那么在这个bd的属性集合中添加一个RuntimeBeanReference
        // 等价于在xml中配置了
        // <property name="sqlSessionFactory" ref="sqlSessionFactoryBeanName"/>
        definition.getPropertyValues().add("sqlSessionFactory",
                                           new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
        // 若是@MapperScan上没有进行配置
        // 那么检查是否为这个bean配置了sqlSessionFactory属性
        // 正常来讲咱们都不会进行配置,会进入自动装配的逻辑
    } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
    }

    // 省略sqlSessionTemplate部分代码
    // 逻辑跟sqlSessionFactory属性的处理逻辑一致
    // 须要注意的是,若是同时显示指定了sqlSessionFactory跟sqlSessionTemplate
    // 那么sqlSessionFactory的配置将失效
    // .....

    if (!explicitFactoryUsed) {
       // 若是没有显示的配置,那么设置为自动注入
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
    // 默认不是懒加载
    definition.setLazyInit(lazyInitialization);
}

}

从上面的代码中咱们不难看到一个最特殊的操做,扫描出来的BeanDefinition并无直接用去建立Bean,而是先将这些BeanDefinition的beanClass属性所有都设置成了MapperFactoryBean,从名字上咱们就能知道他是一个FactoryBean,那么不难猜想确定是经过这个FactoryBean的getObject方法来建立了一个代理对象,咱们查看下这个类的源码:

MapperFactoryBean分析

**继承关系**

![](https://s4.51cto.com/images/blog/202008/10/6d5a1e56a9f40fba4bd088992cf97280.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

咱们重点看下它的两个父类便可

1. DaoSupport:这个类是全部的数据访问对象(DAO)的基类,它定义的全部DAO的初始化模板,它实现了InitializingBean接口,核心方法就是afterPropertiesSet,其源码以下:

public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// 子类能够实现这个方法去检查相关的配置信息
checkDaoConfig();

// 子类能够实现这个方法去进行一些初始化操做
try {
    initDao();
}
catch (Exception ex) {
    throw new BeanInitializationException("Initialization of DAO failed", ex);
}

}

1. SqlSessionDaoSupport:这个类是专门为Mybatis设计的,经过它能获取到一个SqlSession,起源吗以下:

public abstract class SqlSessionDaoSupport extends DaoSupport {

private SqlSessionTemplate sqlSessionTemplate;

// 这个是核心方法
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}

// 省略一些getter/setter方法

// 在初始化时要检查sqlSessionTemplate,确保其不为空br/>@Override
protected void checkDaoConfig() {
notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}

咱们在整合Spring跟Mybatis时,就是调用setSqlSessionFactory完成了对这个类中SqlSessionTemplate的初始化。前面咱们也提到了MapperFactoryBean默认使用的是自动注入,因此在建立每个MapperFactoryBean的属性注入阶段,Spring容器会自动查询是否有跟MapperFactoryBean中setter方法的参数类型匹配的Bean,由于咱们在前面进行了以下配置:
![](https://s4.51cto.com/images/blog/202008/10/8b5e4ddef1df06138b4f693d374e5d94.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

经过咱们配置的这个sqlSessionFactoryBean能获得一个sqlSessionFactory,所以在对MapperFactoryBean进行属性注入时会调用setSqlSessionFactory方法。咱们能够看到setSqlSessionFactory方法内部就是经过sqlSessionFactory建立了一个sqlSessionTemplate。它最终会调用到sqlSessionTemplate的一个构造函数,其代码以下:

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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
    new Class[] { SqlSession.class }, new SqlSessionInterceptor());

}

SqlSessionTemplate自己实现了org.apache.ibatis.session.SqlSession接口,它的全部操做最终都是依赖其成员变量sqlSessionProxy,sqlSessionProxy是经过jdk动态代理生成的,对于动态代理生成的对象其实际执行时都会调用到InvocationHandler的invoke方法,对应到咱们上边的代码就是SqlSessionInterceptor的invoke方法,对应代码以下:

private class SqlSessionInterceptor implements InvocationHandler {br/>@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 第一步,获取一个sqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 第二步,调用sqlSession对应的方法
Object result = method.invoke(sqlSession, args);

// 检查是否开启了事务,若是没有开启事务那么强制提交
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {

            sqlSession.commit(true);
        }
        return result;
    } catch (Throwable t) {
        // 处理异常
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {

            closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            sqlSession = null;
            Throwable translated = SqlSessionTemplate.this.exceptionTranslator
                .translateExceptionIfPossible((PersistenceException) unwrapped);
            if (translated != null) {
                unwrapped = translated;
            }
        }
        throw unwrapped;
    } finally {
        // 关闭sqlSession
        if (sqlSession != null) {
            closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
    }
}

}

咱们再来看看,他在获取SqlSession是如何获取的,不出意外的话确定也是调用了Mybaits的sqlSessionFactory.openssion方法建立的一个sqlSession,代码以下:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {

notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
// 看到了吧,在这里调用了SqlSessionFactory建立了一个sqlSession
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
// 若是开启了事务的话而且事务是由Spring管理的话,会将sqlSession绑定到当前线程上
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

return session;
}

**方法分析**

对于MapperFactoryBean咱们关注下面两个方法就好了

// 以前分析过了,这个方法会在MapperFactoryBean进行初始化的时候调用
protected void checkDaoConfig() {
super.checkDaoConfig();
Configuration configuration = getSqlSession().getConfiguration();
//addToConfig默认为true的,将mapper接口添加到mybatis的配置信息中
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e)
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}

// 简单吧,直接调用了mybatis中现成的方法获取一个代理对象而后放入到容器中br/>@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

> **整合原理总结**

首先咱们知道,Mybatis能够经过下面这种方式直接生成一个代理对象

String resource = "mybatis-config.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

基于这个代理对象,咱们能够执行任意的Sql语句,那么若是Spring想要整合Mybatis,只须要将全部的代理对象管理起来便可,如何作到这一步呢?

这里就用到了Spring提供的一些列扩展点,首先,利用了BeanDefinitionRegistryPostProcessor这个扩展点,利用它的postProcessBeanDefinitionRegistry方法完成了对mapper接口的扫描,并将其注册到容器中,可是这里须要注意的是,它并非简单的进行了扫描,在完成扫描的基础上它将全部的扫描出来的BeanDefinition的beanClass属性都替换成了MapperFactoryBean,这样作的缘由是由于咱们没法根据一个接口来生成Bean,而且实际生成代理对象的逻辑是由Mybatis控制的而不是Spring控制,Spring只是调用了mybatis的API来完成代理对象的建立并放入到容器中,基于这种需求,使用FactoryBean是再合适不过了。

还有经过上面的分析咱们会发现,并非一开始就建立了一个SqlSession对象的,而是在实际方法执行时才会去获取SqlSession的。

# 总结

本文咱们主要学习了Mybatis的基本使用,并对Mybatis的事务管理以及Spring整合Mybatis的原理进行了分析,其中最重要的即是整合原理的分析,以前有小伙伴问我能不能介绍一些实际使用了Spring提供的扩展点的例子,我相信这就是最好的一个例子。

本文为事务专栏的第二篇,之因此特意写一篇mybaits的文章是由于后续咱们不只要分析单独的Spring中的事务管理,还得分析Spring整合Mybatis的事务管理,虽然Spring整合Mybatis后彻底由Spring来进行管理事务,可是咱们要知道Mybatis自身是有本身的事务管理机制的,那么Spring是如何接手的呢?对于这个问题,在后续的文章中我会作详细分析

本文就到这里啦,若是本位对你有帮助的话,记得帮忙三连哈,感谢~!

我叫DMZ,一个在学习路上匍匐前行的小菜鸟!

**往期精选**

Spring官网阅读笔记
Spring杂谈
JVM系列文章
Spring源码专题
![](https://s4.51cto.com/images/blog/202008/10/d2b2b3f6e2ed85edeb57fb04142b7391.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

程序员DMZ
点赞、转发、在看,多谢多谢!
喜欢做者
相关文章
相关标签/搜索