MyBatis-Spring 源码解析

Posted by Kaka Blog on January 23, 2021

版本对应关系

MyBatis-Spring-Boot-Starter MyBatis-Spring Mybatis Spring Framework Spring Boot Java
2.1 2.0 (need 2.0.2+ for enable all features) 3.5+ 5.0+ 2.1 or higher 8 or higher
1.3 1.3 3.4+ 3.2.2+ 1.5 6 or higher

MyBatis-Spring-Boot-Starter

MyBatis-Spring-Boot-Starter可以帮助开发者快速构建基于Spring Boot的MyBatis应用。MyBatis-Spring-Boot-Starter帮助我们实现:

  • 自动检测存在的DataSource
  • 使用SqlSessionFactoryBean创建并注册一个SqlSessionFactory实例,并设置DataSource
  • 创建并注册一个SqlSessionTemplate实例
  • 自动扫描Mappers,关联到SqlSessionTemplate并注册到Spring容器

使用

添加Maven依赖:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>

Mapper类添加@Mapper注解或者使用@MapperScan注解指定包路径

配置文件必须添加:

mybatis.mapper-locations=classpath:/mapper/*Mapper.xml

不然会抛出异常:BindingException: Invalid bound statement (not found)

什么是 MyBatis-Spring?

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

使用

添加Maven依赖:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.6</version>
</dependency>

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>
@Configuration
public class MyBatisConfig {
  @Bean
  public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    return factoryBean.getObject();
  }
}

注意:SqlSessionFactory 需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

假设现在有一个mapper接口,那么可以通过MapperFactoryBean将接口加入到Spring中:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

使用 Java 代码来配置的方式如下:

@Configuration
public class MyBatisConfig {
  @Bean
  public UserMapper userMapper() throws Exception {
    SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
    return sqlSessionTemplate.getMapper(UserMapper.class);
  }
}

什么是MyBatis?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

使用

添加Maven依赖:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

从XML中构建SQLSessionFactory

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。后面会再探讨 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="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

不使用 XML 构建 SqlSessionFactory

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

MyBatis-Spring实现思路

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

(1)MybatisAutoConfiguration自动装配,实例化SqlSessionFactory, SqlSessionFactoryBean是一个工厂Bean, 最后调用SqlSessionFactoryBean#getObject()生成对应的SqlSessionFacatory

(2)生成SqlSessionTemplate,依赖于SqlSessionFacatory实例

(3)在配置@MapperScan注解之后,会调用MapperScannerRegistrar,会创建MapperScannerConfigurer bean定义及对象,然后委派ClassPathMapperScanner根据MapperScan中的值,创建Mapper接口对应的MapperFactoryBean,最后由MapperFactoryBean生成对应的Mapper对象,即使用SqlSession获取对应的Mapper对象.

这就是为什么可以不用实现Mapper接口,就可以实现调用。原因就是Mapper接口对应的实例被换成了MapperProxy。我们指定MapperProxy实现了InvocationHandler,所以调用Mapper接口中的方法走的是MapperProxy的invoke。

核心类介绍

SqlSessionFactory

SqlSessionFactory是MyBatis中的一个重要的对象,它是用来创建SqlSession对象的,而SqlSession用来操作数据库的。

SqlSessionFactoryBean

SqlSessionFactoryBean用来创建SqlSessionFactory对象,读取Mapper XML文件。

SqlSessionTemplate

SqlSessionTemplate是MyBatis-Spring的核心。这个类负责管理MyBatis的SqlSession,调用MyBatis的SQL方法。此外,它管理session的生命周期,包含必要的关闭,提交或回滚操作。

ClassPathMapperScanner

1.设置BeanDefinition的构造函数参数值为当前BeanDefinition的类名,也就是映射接口的类名

definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());

2.改变BeanDefinition的beanClass为MapperFactoryBean.class

definition.setBeanClass(this.mapperFactoryBean.getClass());

做的事情其实是重写BeanDefinition的BeanClass字段为MapperFactoryBean.class,并且将beanClass其实也就是MapperFactoryBean的构造器参数设置为实际的标注了@Mapper的接口,之后当Mapper接口注入的时候,实际调用的是MapperFactoryBean中的getObject()获取特定的mapper实例

MapperFactoryBean

用于根据mapper接口生成mapper对象的类。

Mybatis动态代理实现

使用动态代理

@Autowired
private ArticleMapper articleMapper;
@GetMapping("article/list")
public List<ArticleDO> findAll() {
    System.out.println("articleMapper = " + articleMapper);
    return articleMapper.findAll();
}

不使用动态代理

@Autowired
private SqlSessionFactory factory;

@GetMapping("article/list2")
public List<ArticleDO> findAll2() {
    SqlSession sqlSession = factory.openSession();
    List<ArticleDO> list = sqlSession.selectList("com.fang.web.persistence.dao.ArticleMapper.findAll");
    sqlSession.close();
    return list;
}

动态代理中最重要的类:SqlSession、MapperProxy、MapperMethod。

1、通过SqlSession获取代理对象,实现类为DefaultSqlSession

<T> T getMapper(Class<T> var1);

2、委托Configuration类的getMapper方法

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

3、委托MapperRegistry类的getMapper方法,通过MapperProxyFactory的命名方式我们知道这里将通过这个工厂生成我们所关注的MapperProxy的代理类

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    } else {
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception var5) {
            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
        }
    }
}

4、MapperProxyFactory用来创建动态代理对象

protected T newInstance(MapperProxy<T> mapperProxy) {
    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
    return this.newInstance(mapperProxy);
}

5、MapperProxy的invoke是把Method包装成了MapperMethod,MapperMethod处理了Mapper接口方法与xml映射的关系。

public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return this.mapperMethod.execute(sqlSession, args);
}

总结

总结一下,我们公共MyBatis添加的Mapper的操作实际上添加的是MapperProxyFactory,这个是MapperProxy的工厂类,但是MapperProxyFactory生产的也不是MapperProxy,而是Mapper的Proxy代理。使用的InvocationHandler是MapperProxy,MapperProxy的invoke方法实际上是把Mapper接口方法包装为了MapperMethod,并执行的MapperMethod的execute方法。MapperMethod处理的逻辑是Mapper方法与xml中的SQL的一些映射关系。

参考资料