Mybatis源码解读一:Mybatis整体架构

2023-11-07

本文是mybatis源码阅读计划的第一篇文章,本文简单介绍一下Mybatis简单使用和整体架构。
这篇文章将介绍mybatis的分层架构,主要组件和相关关系,同时简单提及一些核心类核心处理流程。
主要参考资料为mybatis的官网和mybatis源码,示例过程将不是XML配置直接使用SpringBoot的示例项目。

本文源码地址为:https://github.com/zouhuanli/MySpringBoot.git

一、基本介绍和简单使用

Mybatis是一款轻量级的ORM框架,支持高级映射、动态SQL,通过简单的注解或XML来使用简单的Java POJO来操作数据库。动态SQL是Mybatis最显著的优点,极大的提高了SQL操作的灵活性。
使用Mybatis是很简单的,首先mybatis本身可以作为一个独立的框架或者jar使用。只需要应用引入依赖即可。

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

需要与Spring结合时只需加入mybatis-spring依赖。

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

在官网有一个简单的示例代码:

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        Blog blog = mapper.selectBlog(101);
        }

别看示例很简陋,这已经包含了mybatis的全部流程:
通过配置文件构建SqlSessionFactory、Configuration,解析和注册全部的配置(静态配置、Sql、ResultMap等),注册Mapper接口和MapperProxy。通过MapperProxy执行SQL方法,得到执行结果和解析执行结果。

mybatis的源码远没有spring源码复杂,阅读mybatis源码会比spring源码简单。当然两者不能简单比较。
通过SpringBoot使用mybatis只需要引入mybatis-spring-boot-starter。

  <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis-spring-boot-starter.version}</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>${mysql-connector.version}</version>
        </dependency>

和Spring结合主要是创建创建SqlSessionFactory、SqlSessionTemplate,如下。


  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    if (properties.getConfiguration() == null || properties.getConfiguration().getVfsImpl() == null) {
      factory.setVfs(SpringBootVFS.class);
    //......
  }

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
          ExecutorType executorType = this.properties.getExecutorType();
          if (executorType != null) {
          return new SqlSessionTemplate(sqlSessionFactory, executorType);
            } else {
          return new SqlSessionTemplate(sqlSessionFactory);
                }
            }

添加MapperScan自动扫描Mapper接口和注册MapperProxy。

@Configuration
//开启事务
@EnableTransactionManagement(proxyTargetClass = true, mode = AdviceMode.PROXY)
//使用MapperScan注册Mapper接口
@MapperScan(basePackages = "com.homura.myspringboot.dao")
public class MyBatisConfig {
}

二、整体架构

mybatis的分层结构如下:

mybatis_arch

最顶层的主要是SqlSession、SqlSessionFactory等类,创建SqlSession来执行SQL方法。
数据处理层主要是XXXhandler,Sql,Executor等。
支持层主要是XML和注解配置,事务、连接池、缓存。

三、主要组件和关系

Mybatis主要的核心组件有(引用自https://louluan.blog.csdn.net/article/details/40422941):

  • SqlSessionFactory:SqlSession的创建工厂,负责创建SqlSession
  • SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
  • Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
  • StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
  • ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数,
  • ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
  • TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换
  • MappedStatement:MappedStatement维护了一条<select update delete insert>节点的封装,
  • SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
  • BoundSql:表示动态生成的SQL语句以及相应的参数信息
  • Configuration:MyBatis所有的配置信息都维持在Configuration对象之中。

这些就是mybatis主要的核心类。

解读Mybatis的源码主要把握两个脉络:

  • 1.SqlSessionFactory的构造过程,包括创建SqlSessionFactory、Configuration、Mapper接口注册、SQL解析和注册、MapperProxy的创建。这些主要是应用启动时候全部加载,解析,注册完成的。
  • 2.SQL方法的执行过程,具体指的是MapperProxy的invoke方法开始涉及的SqlSession、SqlSessionTemplate、Executor、XXXHandler等对SQL方法的执行处理过程。这些是具体执行一个SQL方法的流程。

四、核心流程

4.1 初始化和SqlSessionFactory创建

SqlSessionFactory的创建的入口是MybatisAutoConfiguration#sqlSessionFactory方法。

@Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
                    //通过MybatisProperties这个配置Bean设置SqlSessionFactoryBean的各项配置值
        //......
                                // SqlSessionFactoryBean#getObject方法创建SqlSessionFactory
    return factory.getObject();
  }

在SqlSessionFactoryBean#buildSqlSessionFactory是创建SqlSessionFactory的真正的方法。单独使用mybatis通过SqlSessionFactoryBuilder创建SqlSessionFactory。

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {		
				//创建Configuration
    final Configuration targetConfiguration;
				//加载、解析、注册各项配置,也会扫描Mapper

				//创建SqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

Configuration是一个非常大的配置类,注册了Mybatis的全部配置信息。

4.2 MapperProxy的创建过程

我们看下一个普通的Mapper接口。


@Repository
public interface UserMapper {
    /**
     * 查找全部用户
     * <p>
     * 不要使用 select  *
     *
     * @return
     */
    @Select("select * from user")
    @ResultMap("BaseResultMap")
    List<User> findAll();

    /**
     * 添加用户
     *
     * @param user 用户
     * @return
     */
    @Insert(" insert  into user (uid,uname,password,gender,phone,email,address) " +
            " values " +
            " (#{user.uid},#{user.uname},#{user.password},#{user.gender},#{user.phone},#{user.email},#{user.address})")
    @ResultType(int.class)
    int save(@Param("user") User user);


    /**
     * 根据id查询用户
     * 不要使用 select  *
     *
     * @param uid 用户id
     * @return
     */
    @Select("select * from user where uid = #{uid}")
    @Results(id = "BaseResultMapByAnnotation", value =
            {@Result(id = true, property = "uid", column = "uid", jdbcType = JdbcType.INTEGER)
                    , @Result(property = "uname", column = "uname", jdbcType = JdbcType.VARCHAR)
                    , @Result(property = "password", column = "password", jdbcType = JdbcType.VARCHAR)
                    , @Result(property = "gender", column = "gender", jdbcType = JdbcType.VARCHAR)
                    , @Result(property = "phone", column = "phone", jdbcType = JdbcType.VARCHAR)
                    , @Result(property = "email", column = "email", jdbcType = JdbcType.VARCHAR)
                    , @Result(property = "address", column = "address", jdbcType = JdbcType.VARCHAR)})
    User selectByPrimaryKey(@Param("uid") Integer uid);
}

要使用该Mapper接口,可以直接使用Spring自动注入依赖接口,其原理是启动时自动创建MapperProxy注册到Spring容器。首先接口方法肯定没法直接执行的,mybatis是对每一个接口创建一个MapperProxy这样的代理对象来代理所有的接口方法。
session.getMapper方法最终会来到下面代码,来创建MapperProxy代理对象。

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

而knownMappers是应用启动时候创建SqlSessionFactory时候注册所有的Mapper接口到这个集合。
和Spring结合使用会自动扫描Mapper路径和注册MapperProxy,启动时通过ClassPathMapperScanner#processBeanDefinitions方法的注册MapperProxyFactoryBean,MapperProxyFactoryBean是一类factoryBean, MapperProxyFactoryBean最终在Spring容器注册的是MapperProxy对象,其源码如下。

                            //注意看这个注释
        // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      try {
        // for spring-native,设置mapper的接口类型
        definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
      } catch (ClassNotFoundException ignore) {
        // ignore
      }
                            //bean的类型是MapperProxyFactoryBean
      definition.setBeanClass(this.mapperFactoryBeanClass);

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

      // Attribute for MockitoPostProcessor
      // https://github.com/mybatis/spring-boot-starter/issues/475
      definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);

      boolean explicitFactoryUsed = false;
                            //注入sqlSessionFactory
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory",
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }
                            //注入sqlSessionTemplate
      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          LOGGER.warn(
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate",
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          LOGGER.warn(
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

MapperProxy的创建流程如下:

MapperFactoryBean_getObject

4.3 SQL的执行流程

上面已经创建好了MapperProxy,执行Mapper接口的方法自然就进入了MapperProxy的方法。如下。

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      }
      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

SQL方法的执行流程如下:

MapperProxy_invoke

五、参考材料

1.https://mybatis.org/mybatis-3/zh/
2.Mybatis源码(版本3.5.13)
3.https://louluan.blog.csdn.net/article/details/40422941