凌月风的个人博客

记录精彩人生

Open Source, Open Mind,
Open Sight, Open Future!
  menu

Java笔记系列——03-源码分析(Mybatis)

0 浏览

MyBatis简介

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • 官网地址:https://mybatis.org/mybatis-3/zh/index.html

  • Mybatis使用

    • 添加配置文件

      <?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>
          <properties resource="db.properties"></properties>
          <settings>
              <!-- 打印查询语句 -->
              <setting name="logImpl" value="STDOUT_LOGGING" />
              <!-- 控制全局缓存(二级缓存),默认 true-->
              <setting name="cacheEnabled" value="false"/>
              <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false -->
              <setting name="lazyLoadingEnabled" value="true"/>
              <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签
          的 fetchType来覆盖-->
              <setting name="aggressiveLazyLoading" value="true"/>
              <!-- Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认JAVASSIST -->
              <!--<setting name="proxyFactory" value="CGLIB" />-->
              <!-- STATEMENT级别的缓存,使一级缓存,只针对当前执行的这一statement有效 -->
              <!--
          <setting name="localCacheScope" value="STATEMENT"/>
          -->
              <setting name="localCacheScope" value="SESSION"/>
          </settings>
          <typeAliases>
              <typeAlias alias="user" type="com.gupaoedu.domain.User" />
          </typeAliases>
          <environments default="development">
              <environment id="development">
                  <transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
                  <dataSource type="POOLED">
                      <property name="driver" value="${jdbc.driver}"/>
                      <property name="url" value="${jdbc.url}"/>
                      <property name="username" value="${jdbc.username}"/>
                      <property name="password" value="${jdbc.password}"/>
                  </dataSource>
              </environment>
          </environments>
          <mappers>
              <mapper resource="mapper/UserMapper.xml"/>
          </mappers>
      </configuration>
      
    • 创建POJO

      public class User {
          private Integer id;
          private String userName;
          private String realName;
          private String password;
          private Integer age;
          private Integer dId;
      }
      
    • 接口声明

      public interface UserMapper {
          public List<User> selectUserList();
      }
      
    • 创建映射器

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.gupaoedu.vip.mapper.UserMapper">
      
          <resultMap id="BaseResultMap" type="user">
              <id property="id" column="id" jdbcType="INTEGER"/>
              <result property="userName" column="user_name" jdbcType="VARCHAR" />
              <result property="realName" column="real_name" jdbcType="VARCHAR" />
              <result property="password" column="password" jdbcType="VARCHAR"/>
              <result property="age" column="age" jdbcType="INTEGER"/>
              <result property="dId" column="d_id" jdbcType="INTEGER"/>
          </resultMap>
          <select id="selectUserList" resultMap="BaseResultMap" >
              select * from t_user
          </select>
      </mapper>
      
    • 调用接口

      public void test2() throws Exception{
          // 1.获取配置文件
          InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
          // 2.加载解析配置文件并获取SqlSessionFactory对象
          SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
          // 3.根据SqlSessionFactory对象获取SqlSession对象
          SqlSession sqlSession = factory.openSession();
          // 4.通过SqlSession中提供的 API方法来操作数据库
          UserMapper mapper = sqlSession.getMapper(UserMapper.class);
          List<User> list = mapper.selectUserList();
          for (User user : list) {
              System.out.println(user);
          }
          // 5.关闭会话
          sqlSession.close();
      }
      

功能特性

  • 全局配置:控制MyBatis 行为的设置和属性信息 配置文档的顶层结如下

    • Configuration(配置)

      • 整个配置文件的根标签,实际上也对应着MyBatis里面最重要的配置类Configuration。它贯穿MyBatis执行流程的每一个环节

    • properties(属性)

      • 第一个一级标签是properties,用来配置参数信息,比如最常见的数据库连接信息

      • 为了避免直接把参数写死在xml配置文件中,我们可以把这些参数单独放在properties文件中,用properties标签引入进来,然后在xml配置文件中用${}引用就可以了。可以用resource引用应用里面的相对路径,也可以用url指定本地服务器或者网络的绝对路径

        <properties resource="db.properties"></properties>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
                <dataSource type="POOLED">
                    <property name="driver" value="${jdbc.driver}"/>
                    <property name="url" value="${jdbc.url}"/>
                    <property name="username" value="${jdbc.username}"/>
                    <property name="password" value="${jdbc.password}"/>
                </dataSource>
            </environment>
        </environments>
        

    • settings(设置)

      • 这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为image-20220716110329236

    • typeAliases(类型别名)

      • 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

        <!--当这样配置时,`Blog` 可以用在任何使用 `domain.blog.Blog` 的地方-->
        <typeAliases>
          <typeAlias alias="Blog" type="domain.blog.Blog"/>
        </typeAliases>
        
      • 可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean

        <typeAliases>
          <package name="domain.blog"/>
        </typeAliases>
        
      • MyBatis里面有很多系统预先定义好的类型别名,在TypeAliasRegistry中。所以可以用string代替java.lang.String。

        public TypeAliasRegistry() {
            registerAlias("string", String.class);
            registerAlias("byte", Byte.class);
            // 省略部分代码......
        }
        

    • typeHandlers(类型处理器)

      • MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型

      • MyBatis已经内置了很多TypeHandler(在type包下),它们全部全部注册在TypeHandlerRegistry中,他们都继承了抽象类BaseTypeHandler,泛型就是要处理的Java数据类型。当我们查询数据和登记数据,做数据类型转换的时候,就会自动调用对应的TypeHandler的方法。

      • 自定义类型处理器:如果查询结果字段是一个字符串,且值为"zhangsan"就修饰下这个信息

        1. 创建自定义处理类

          public class MyTypeHandler extends BaseTypeHandler<String> {
              /**
          		* 插入数据的时候回调的方法
          		* @param ps
          		* @param i
          		* @param parameter
          		* @param jdbcType
          		* @throws SQLException
          	*/
              @Override
              public void setNonNullParameter(PreparedStatement ps, int i, String
                                              parameter, JdbcType jdbcType) throws SQLException {
                  System.out.println("---------------setNonNullParameter1:"+parameter);
                  ps.setString(i, parameter);
              }
              @Override
              public String getNullableResult(ResultSet rs, String columnName) throws
                  SQLException {
                  String name = rs.getString(columnName);
                  if("zhangsan".equals(name)){
                      return name+"666";
                  }
                  return name;
              }
              @Override
              public String getNullableResult(ResultSet rs, int columnIndex) throws
                  SQLException {
                  String name = rs.getString(columnIndex);
                  if("zhangsan".equals(name)){
                      return name+"666";
                  }
                  return name;
              }
              @Override
              public String getNullableResult(CallableStatement cs, int columnIndex)
                  throws SQLException {
                  String name = cs.getString(columnIndex);
                  if("zhangsan".equals(name)){
                      return name+"666";
                  }
                  return name;
              }
          }
          
        2. 注册自定义处理类

          <typeHandlers>
              <typeHandler handler="com.example.type.MyTypeHandler"></typeHandler>
          </typeHandlers>
          

    • objectFactory(对象工厂)

      • 每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。

      • 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现

        1. 创建自定义对象工厂

          public class MyObjectFactory extends DefaultObjectFactory {
              @Override
              public Object create(Class type) {
                  System.out.println("创建对象方法:" + type);
                  if (type.equals(User.class)) {
                      User blog = (User) super.create(type);
                      blog.setUserName("object factory");
                      blog.setId(1111);
                      blog.setRealName("张三");
                      return blog;
                  }
                  Object result = super.create(type);
                  return result;
              }
          
        2. 注册自定义工厂

          <objectFactory type="com.example.objectfactory.MyObjectFactory">
              <!-- 创建对象时可以传入属性值 -->
              <property name="name" value="666"/>
          </objectFactory>
          

    • plugins(插件)

      • 插件是MyBatis的一个很强大的机制。跟很多其他的框架一样,MyBatis预留了插件的接口,让MyBatis更容易扩展

    • environments(环境配置)

      • environments标签用来管理数据库的环境,比如我们可以有开发环境、测试环境、生产环境的数据库。可以在不同的环境中使用不同的数据库地址或者类型

      • 一个environment标签就是一个数据源,代表一个数据库,每个数据库对应一个 SqlSessionFactory 实例。

        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
                <dataSource type="POOLED">
                    <property name="driver" value="${jdbc.driver}"/>
                    <property name="url" value="${jdbc.url}"/>
                    <property name="username" value="${jdbc.username}"/>
                    <property name="password" value="${jdbc.password}"/>
                </dataSource>
            </environment>
        </environments>
        
      • 环境变量中还包含transactionManager(事务管理器)、dataSource(数据源)databaseIdProvider(数据库厂商标识)可以查看官网介绍


    • mappers(映射器)

      • 标签配置的是映射器,也就是Mapper.xml的路径。这里配置的目的是让MyBatis在启动的时候去扫描这些映射器,创建映射关系

      • 使用时有四种指定Mapper文件的方式

        <!-- 使用相对于类路径的资源引用 -->
        <mappers>
          <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
        </mappers>
        <!-- 使用完全限定资源定位符(URL) -->
        <mappers>
          <mapper url="file:///var/mappers/AuthorMapper.xml"/>
        </mappers>
        <!-- 使用映射器接口实现类的完全限定类名 -->
        <mappers>
          <mapper class="org.mybatis.builder.AuthorMapper"/>
        </mappers>
        <!-- 将包内的映射器接口实现全部注册为映射器 -->
        <mappers>
          <package name="org.mybatis.builder"/>
        </mappers>
        

  • 映射器配置

    • cache: 该命名空间的缓存配置。

    • cache-ref: 引用其它命名空间的缓存配置。

    • resultMap:是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。

      <resultMap id="BaseResultMap" type="Employee">
          <id column="emp_id" jdbcType="INTEGER" property="empId"/>
          <result column="emp_name" jdbcType="VARCHAR" property="empName"/>
          <result column="gender" jdbcType="CHAR" property="gender"/>
          <result column="email" jdbcType="VARCHAR" property="email"/>
          <result column="d_id" jdbcType="INTEGER" property="dId"/>
      </resultMap>
      
    • sql:可被其他语句引用的可重用语句块。

      <sql id="Base_Column_List">
          emp_id, emp_name, gender, email, d_id
      </sql>
      
    • 增删改查标签

      <select> - 映射查询语句
      <insert> – 映射插入语句
      <update> – 映射更新语句
      <delete> – 映射删除语句
      

  • 动态SQL

    • 动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

    • 动态SQL主要是用来解决SQL语句生成的问题。

    • if:需要判断的时候,条件写在test中

      <select id="selectListIf" parameterType="user" resultMap="BaseResultMap" >
          select
          <include refid="baseSQL"></include>
          from t_user
          <where>
              <if test="id != null">
                  and id = #{id}
              </if>
              <if test="userName != null">
                  and user_name = #{userName}
              </if>
          </where>
      </select>
      
    • choose、when、otherwise:需要选择一个条件的时候

      <!-- choose 的使用 -->
      <select id="selectListChoose" parameterType="user" resultMap="BaseResultMap"
              >
          select
          <include refid="baseSQL"></include>
          from t_user
          <where>
              <choose>
                  <when test="id != null">
                      id = #{id}
                  </when>
                  <when test="userName != null and userName != ''">
                      and user_name like CONCAT(CONCAT('%',#
                      {userName,jdbcType=VARCHAR}),'%')</when>
                  <otherwise>
                  </otherwise>
              </choose>
          </where>
      </select>
      
    • trim:需要去掉where、and、逗号之类的符号的时

      <!--trim 的使用替代where标签的使用-->
      <select id="selectListTrim" resultMap="BaseResultMap"
              parameterType="user">
          select <include refid="baseSQL"></include>
      
          <trim prefix="where" prefixOverrides="AND |OR ">
              <if test="userName!=null">
                  and user_name = #{userName}
              </if>
              <if test="age != 0">
                  and age = #{age}
              </if>
          </trim>
      </select>
      <!-- 替代set标签的使用 -->
      <update id="updateUser" parameterType="User">
          update t_user
          <trim prefix="set" suffixOverrides=",">
              <if test="userName!=null">
                  user_name = #{userName},
              </if>
              <if test="age != 0">
                  age = #{age}
              </if>
          </trim>
          where id=#{id}
      </update>
      
    • foreach:需要遍历集合的时候

      <delete id="deleteByList" parameterType="java.util.List">
          delete from t_user
          where id in
          <foreach collection="list" item="item" open="(" separator="," close=")">
              #{item.id,jdbcType=INTEGER}
          </foreach>
      </delete>
      

  • 批量操作

    • 批量插入

      <!-- 批量插入 -->
      <insert id="insertUserList" parameterType="java.util.List" >
          insert into t_user(user_name,real_name)
          values
          <foreach collection="list" item="user" separator=",">
              (#{user.userName},#{user.realName})
          </foreach>
      </insert>
      
    • 批量更新

      <update id="updateUserList">
          update t_user set
          user_name =
          <foreach collection="list" item="user" index="index" separator=" "
                   open="case id" close="end">
              when #{user.id} then #{user.userName}
          </foreach>
          ,real_name =
          <foreach collection="list" item="user" index="index" separator=" "
                   open="case id" close="end">
              when #{user.id} then #{user.realName}
          </foreach>
          where id in
          <foreach collection="list" item="item" open="(" separator=","
                   close=")">
              #{item.id,jdbcType=INTEGER}
          </foreach>
      </update>
      
    • 批量删除

      <delete id="deleteByList" parameterType="java.util.List">
          delete from t_user where id in
          <foreach collection="list" item="item" open="(" separator="," close=")">
              #{item.id,jdbcType=INTEGER}
          </foreach>
      </delete>
      
    • 当数据量特别大的时候,拼接出来的SQL语句过大。 MySQL的服务端对于接收的数据包有大小限制,max_allowed_packet 默认是 4M,需要修改默认配置或者手动地控制条数,才可以解决这个问题。

    • 在我们的全局配置文件中,可以配置默认的Executor的类型(默认是SIMPLE)。其中有一种BatchExecutor。

      • 执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。

      • 与JDBC批处理相同。executeUpdate()是一个语句访问一次数据库,executeBatch()是一批语句访问一次数据库(具体一批发送多少条SQL跟服务端的max_allowed_packet有关)。BatchExecutor底层是对JDBC ps.addBatch()和ps. executeBatch()的封装。

        <setting name="defaultExecutorType" value="BATCH" />
        

  • 插件机制

    • 插件是一种常见的扩展方式,大多数开源框架也都支持用户通过添加自定义插件的方式来扩展或者改变原有的功能。

    • MyBatis中也提供的有插件,虽然叫插件,但是实际上是通过拦截器(Interceptor)实现的,在MyBatis的插件模块中涉及到责任链模式和JDK动态代理

    • 自定义插件

      • 创建的拦截器必须要实现Interceptor接口。

        public interface Interceptor {
            // 执行拦截逻辑的方法
            Object intercept(Invocation invocation) throws Throwable;
            // 决定是否触发 intercept()方法
            default Object plugin(Object target) {
                return Plugin.wrap(target, this);
            }
            // 根据配置 初始化 Intercept 对象
            default void setProperties(Properties properties) {
                // NOP
            }
        }
        
      • 在MyBatis中Interceptor允许拦截的内容是

        • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
        • ParameterHandler (getParameterObject, setParameters)
        • StatementHandler (prepare, parameterize, batch, update, query)
        • ResultSetHandler (handleResultSets, handleOutputParameters)
      • 创建自定义插件

        1. 创建Interceptor实现类,为实现类加入相关注解

          @Intercepts({
              @Signature(
                  type = Executor.class // 需要拦截的类型
                  ,method = "query" // 需要拦截的方法
                  // args 中指定 被拦截方法的 参数列表
                  ,args={MappedStatement.class,Object.class, RowBounds.class,
                         ResultHandler.class}
              ),
              @Signature(
                  type = Executor.class
                  ,method = "close"
                  ,args = {boolean.class}
              )
          })
          public class FirstInterceptor implements Interceptor {
              private int testProp;
              /**
              * 执行拦截逻辑的方法
              * @param invocation
              * @return
              * @throws Throwable
              */
              @Override
              public Object intercept(Invocation invocation) throws Throwable {
                  System.out.println("FirtInterceptor 拦截之前 ....");
                  Object obj = invocation.proceed();
                  System.out.println("FirtInterceptor 拦截之后 ....");
                  return obj;
              }
              /**
              * 决定是否触发 intercept方法
              * @param target
              * @return
              */
              @Override
              public Object plugin(Object target) {
                  return Plugin.wrap(target,this);
              }
              @Override
              public void setProperties(Properties properties) {
                  System.out.println("---->"+properties.get("testProp"));
              }
              public int getTestProp() {
                  return testProp;
              }
              public void setTestProp(int testProp) {
                  this.testProp = testProp;
              }
          }
          
        2. 配置拦截器

          <plugins>
              <plugin interceptor="com.gupaoedu.interceptor.FirstInterceptor">
                  <property name="testProp" value="1000"/>
              </plugin>
          </plugins>
          

  • 插件原理

    • 初始化时通过解析配置文件,根据自定义的插件创建Interceptor对象,并且封装对应的属性信息。自定义的拦截器最终是保存在了InterceptorChain这个对象中

    • 在创建Executor 、ParameterHandler、ResultSetHandler、StatementHandler时会调用InterceptorChain的 pluginAll方法对与创建的对象进行层层代理

    • 单个拦截器的执行流程image-20220716160757888

    • 多个拦截器时,配置的顺序和执行的顺序是相反的。

      • InterceptorChain的List是按照插件从上往下的顺序解析、添加的。

      • 而创建代理的时候也是按照list的顺序代理。执行的时候当然是从最后代理的对象开始。

        image-20220716160849345


核心原理

  • Mybatis是一款框架,底层针对JDBC的封装,既然如此还是逃不过JDBC的步骤。
    1. 注册驱动
    2. 创建连接Connection
    3. 创建Statement对象
    4. 根据Statement对象执行sql
    5. 获取ResultSet 结果集
    6. 顺序关闭ResultSet 、Statement、Connection

  • JDBC步骤与Mybatis步骤对比来看

    image-20220724145550570


  • 核心对象

    对象相关对象作用
    ConfigurationMapperRegistryTypeAliasRegistryTypeHandlerRegistry包含了MyBatis的所有的配置信息
    SqlSessionSqlSessionFactoryDefaultSqlSession对操作数据库的增删改查的API进行了封装,提供给应用层使用
    ExecutorBaseExecutorSimpleExecutorBatchExecutorReuseExecutorMyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
    StatementHandlerBaseStatementHandlerSimpleStatementHandlerPreparedStatementHandlerCallableStatementHandler封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合
    ParameterHandlerDefaultParameterHandler把用户传递的参数转换成JDBCStatement 所需要的参数
    ResultSetHandlerDefaultResultSetHandler把JDBC返回的ResultSet结果集对象转换成List类型的集合
    MapperProxyMapperProxyFactory触发管理类,用于代理Mapper接口方法
    MappedStatementSqlSourceBoundSqlMappedStatement维护了一条节点的封装,表示一条SQL包括了SQL信息、入参信息、出参信息

  • MyBatis整体架构分为三层:接口层、核心处理层、基础支持层

    image-20220702175921648


    • 反射模块
      • MyBatis在进行参数处理、结果集映射等操作时会使用到大量的反射操作,Java中的反射功能虽然强大,但是代码编写起来比较复杂且容易出错,为了简化反射操作的相关代码,MyBatis提供了专门的反射模块,
      • 该模块位于org.apache.ibatis.reflection包下,它对常见的反射操作做了进一步的封装,提供
        了更加简洁方便的反射API。
      • 在创建SqlSessionFactory操作的时候会完成Configuration对象的创建,而在Configuration中默认
        定义的ReflectorFactory的实现就是DefaultReflectorFactory对象
      • 在Statement获取结果集后,在做结果集映射的使用有使用到,在DefaultResultSetHandler的
        createResultObject方法中。

    • binding模块
      • binding模块 包含了几个重要的对象。在映射文件加载解析时会使用该模块完成Mapper接口的注册。在调用过程中使用 sqlSession.getMapper(UserMapper.class);时,会通过该模块的 对象MapperRegistry获取MapperProxyFactory对象,由MapperProxyFactory对象创建Mapper接口对应的代理对象

    • 类型转换模块

      • 实现数据库中数据和Java对象中的属性的双向映射,那么不可避免的就会碰到类型转换的问题,在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换为JDBC类型,而从结果集中获取数据时,则需要从JDBC类型转换为Java类型
        image-20220811115222112

      • MyBatis中的所有的类型转换器都继承了TypeHandler接口,在TypeHandler中定义了类型转换器的最基本的功能。

        public interface TypeHandler<T> {
            /**
        * 负责将Java类型转换为JDBC的类型
        * 本质上执行的就是JDBC操作中的 如下操作
        * String sql = "SELECT id,user_name,real_name,password,age,d_id from
        t_user where id = ? and user_name = ?";
        * ps = conn.prepareStatement(sql);
        * ps.setInt(1,2);
        * ps.setString(2,"张三");
        * @param ps
        * @param i 对应占位符的 位置
        * @param parameter 占位符对应的值
        * @param jdbcType 对应的 jdbcType 类型
        * @throws SQLException
        */
            void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
            /**
        * 从ResultSet中获取数据时会调用此方法,会将数据由JdbcType转换为Java类型
        */
            T getResult(ResultSet rs, String columnName) throws SQLException;
            T getResult(ResultSet rs, int columnIndex) throws SQLException;
            T getResult(CallableStatement cs, int columnIndex) throws SQLException;
        }
        
      • 在Configuration对象实例化的时候在成员变量中完成了TypeHandlerRegistry和TypeAliasRegistry的实例化。

      • TypeHandler类型处理器使用比较多的地方应该是在给SQL语句中参数绑定值和查询结果和对象中属性映射的地方用到的比较多,


    • 日志模块

      • MyBatis定义了一套统一的日志接口供上层使用。Log接口中定义了四种日志级别

        public interface Log {
            boolean isDebugEnabled();
            boolean isTraceEnabled();
            void error(String s, Throwable e);
            void error(String s);
            void debug(String s);
            void trace(String s);
            void warn(String s);
        }
        
      • LogFactory工厂类负责创建日志组件适配器,在LogFactory类加载时会执行其静态代码块,其逻辑是按序加载并实例化对应日志组件的适配器,然后使用LogFactory.logConstructor这个静态字段,记录当前使用的第三方日志组件的适配器


    • 缓存模块

      • 缓存模块中最核心的接口是Cache接口,位于 org.apache.ibatis.cache包下,它定义了所有缓存的基本行为。Cache接口的实现类很多,但是大部分都是装饰器,只有PerpetualCache提供了Cache接口的基本实现。这些装饰器都在PerpetualCache的基础上提供了一些额外的功能,通过多个组合实现一些特殊的需求。image-20220715132555741

      • 在Configuration初始化的时候会为我们的各种Cache实现注册对应的别名

      • MyBatis中的缓存分为一级缓存和二级缓存

        • 一级缓存也叫本地缓存(Local Cache),MyBatis的一级缓存是在会话(SqlSession)层面进行缓存的。

          • MyBatis的一级缓存是默认开启的,不需要任何的配置(如果要关闭,localCacheScope设置为STATEMENT)。在BaseExecutor对象的query方法中有关闭一级缓存的逻辑
          • 因为一级缓存是Session级别的缓存,肯定需要在Session范围内创建,其实PerpetualCache的实例化是在BaseExecutor的构造方法中创建的
        • 二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是namespace级别的,位于Configuration对象可以被多个SqlSession共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。

          1. 二级缓存的设置,首先是settings中的cacheEnabled要设置为true,当然默认的就是为true,这个步骤决定了在创建Executor对象的时候是否通过CachingExecutor来装饰

          2. 设置了cacheEnabled标签为true,还需要在 对应的映射文件中添加 cache 标签才行。

            <!-- 
            	声明这个namespace使用二级缓存
             	最多缓存对象个数,默认1024
            	回收策略
            	自动刷新时间 ms,未配置时只有调用时刷新
            	默认是false(安全),改为true可读写时,对象必须支持序列化
            -->
            <cache type="org.apache.ibatis.cache.impl.PerpetualCache"
                   size="1024" 
                   eviction="LRU"
                   flushInterval="120000" 
                   readOnly="false"/>
            
          3. 如果某些个别方法我们不希望走二级缓存,则可以在标签中添加一个 useCache=false来实现的设置不使用二级缓存


源码分析

  • 步骤总结

    1. 读取MyBatis的配置文件,将配置文件的信息封装为Configuration 对象

    2. 其中比较重要的是,将Mapper映射文件分解封装为MapperdStatement对象。

    3. 通过Configuration 对象创建会话工厂SqlSessionFactory对象

    4. 通过会话工厂创建SqlSession对象

      • 创建SqlSession对象过程中会创建Executor对象
      • Executor对象有许多子类,如果启用了二级缓存会创建CacheExecutor
      • 通过插件机制对Executor层层代理,最后返回代理对象
    5. 通过SqlSession 获取Mapper

      • Mapper对象的获取,是通过MapperProxy对象,根据接口生成的代理对象
    6. Mapper执行具体方法

      • 通过代理的Mapper对象执行调用,最终会执行到MapperMethod对象的 execute()方法
      • execute()方法实际调用的是DefaultSqlSession的方法
    7. 在DefaultSqlSession方法中完成最终的语句执行

      • 创建StatementHandler对象,通过Handler对象生成具体的Statement 对象
      • StatementHandler生成也会经过插件的层层代理
      • 由Statement 对象执行SQL语句,完成调用
    8. 最终的返回结果会经过ResultS'e'tHandler进行封装处理转换

      image-20220724144722065


  • 通过配置文件流的形式,以构建这模式构建SqlSessionFactory对象。

    public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
    }
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        // 省略部分代码......
        // 创建配置文件解析器,创建过程已经完成了DOM解析器的创建,使用的是java官方的DOM解析
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        return build(parser.parse());
        // 省略部分代码......
    }
    
  • 先根据传入的文件流信息,构建对应的配置文件解析器XMLConfigBuilder,然后解析文件信息,封装为Configuration对象

    public Configuration parse() {
        // 标志位,配置文件只解析一次
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
    private void parseConfiguration(XNode root) {
        try {
            // issue #117 read properties first
            // 属性
            propertiesElement(root.evalNode("properties"));
            // 设置
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            loadCustomVfs(settings);
            loadCustomLogImpl(settings);
            // 类型别名
            typeAliasesElement(root.evalNode("typeAliases"));
            // 插件
            pluginElement(root.evalNode("plugins"));
            // 对象工厂
            objectFactoryElement(root.evalNode("objectFactory"));
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 全局设置
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            // 环境配置
            environmentsElement(root.evalNode("environments"));
            // 数据厂商
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 类型处理器
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 映射Mapper,最终封装成许多MapperStatement对象,供后续使用
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
    
  • 根据Configuration对象创建SqlSessionFactory对象,直接返回DefaultSqlSessionFactory对象

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
    
  • 根据SqlSessionFactory对象获取SqlSession,最终返回了DefaultSqlSession的实例

    public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
            // 获取数据源配置
            final Environment environment = configuration.getEnvironment();
    		// 根据数据源配置创建事务管理工厂
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            // 根据工厂管理器创建事务管理器,ManagedTransaction
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 根据事务、批量执行还是单条执行,创建执行器,如果开启类缓存会进行包装,最终加入了插件的逻辑
            final Executor executor = configuration.newExecutor(tx, execType);
            // 返回SqlSession
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            closeTransaction(tx); // may have fetched a connection so lets call close()
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
    
  • 当获取Mapper映射对象时执行 getMapper()方法,执行了SqlSession的属性Configuration的 getMapper()方法

    public <T> T getMapper(Class<T> type) {
        return configuration.getMapper(type, this);
    }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
    }
    
  • getMapper()通过代理方式创建新的Mapper实现对象

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // 查看是否是已知的Mapper接口
        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);
        }
    }
    
    public T newInstance(SqlSession sqlSession) {
        // 创建MapperProxy 对象,实际上就是Jdk动态代理中的InvocationHandler的实现类
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
    
    protected T newInstance(MapperProxy<T> mapperProxy) {
        // jdk动态代理创建代理对象并返回
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }
    
  • 获取到Mapper对象之后,然后执行具体的方法就会调用代理类的 invoke()方法

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 如果传入的是实现类,那么直接调用实现类的方法
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else {
                // 如果传入的是接口,则先执行cachedInvoker(method)
                return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
            // 查找methodCache 是否有此方法,如果没有则加入,有则返回
            return MapUtil.computeIfAbsent(methodCache, method, m -> {
                // 方法是否是default修饰
                if (m.isDefault()) {
                    try {
                        if (privateLookupInMethod == null) {
                            return new DefaultMethodInvoker(getMethodHandleJava8(method));
                        } else {
                            return new DefaultMethodInvoker(getMethodHandleJava9(method));
                        }
                    } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                             | NoSuchMethodException e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    // 关键代码是在这。MapperMethod初始化时会生成sqlCommond对象
                    return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
                }
            });
        } catch (RuntimeException re) {
            Throwable cause = re.getCause();
            throw cause == null ? re : cause;
        }
    }
    
  • 最终的执行是通过PlainMethodInvoker 对象的 invoke()来执行的

    private static class PlainMethodInvoker implements MapperMethodInvoker {
        private final MapperMethod mapperMethod;
    
        public PlainMethodInvoker(MapperMethod mapperMethod) {
            super();
            this.mapperMethod = mapperMethod;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
            // 关键执行代码
            return mapperMethod.execute(sqlSession, args);
        }
    }
    
  • PlainMethodInvoker实际上调用的是MapperMethod对象的 execute()方法

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case INSERT: {
                Object param = method.convertArgsToSqlCommandParam(args);
                // 关键代码
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                // 关键代码
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                // 关键代码
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                    // 关键代码
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    // 关键代码
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    // 关键代码
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {
                    // 关键代码
                    result = executeForCursor(sqlSession, args);
                } else {
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // 关键代码
                    result = sqlSession.selectOne(command.getName(), param);
                    if (method.returnsOptional()
                        && (result == null || !method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("");
        }
        return result;
    }
    
  • 分析update执行步骤,MapperMethod对象 创建时,会生成SqlCommand对象,会在该对象中获取要执行的语句对应的MappedStatement的ID

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
        // Configuration初始化时生成的Mapper映射文件中的标签对相应的对象,比如select 对应一个MappedStatement
        MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
                                                    configuration);
        // 省略部分代码......
        name = ms.getId();
        // 省略部分代码......
    }
    
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        // 省略部分代码......
        Object param = method.convertArgsToSqlCommandParam(args);
        // 关键代码 command.getName()是MappedStatement 的ID
        result = rowCountResult(sqlSession.update(command.getName(), param));
        // 省略部分代码......
        return result;
    }
    
    public int update(String statement, Object parameter) {
        try {
            dirty = true;
            // 获取配置文件中的MappedStatement
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.update(ms, wrapCollection(parameter));
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
    
  • 在Executor中执行具体逻辑

    public int update(MappedStatement ms, Object parameter) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        clearLocalCache();
        return doUpdate(ms, parameter);
    }
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            // 获取statementHandler
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
            // 生成具体的 Statement
            stmt = prepareStatement(handler, ms.getStatementLog());
            return handler.update(stmt);
        } finally {
            closeStatement(stmt);
        }
    }
    
    // 执行语句逻辑
    public int update(Statement statement) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        int rows = ps.getUpdateCount();
        Object parameterObject = boundSql.getParameterObject();
        KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
        keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
        return rows;
    }
    
  • 生成Statement 的过程解析,会根据Mapper配置中的标签属性决定哪一种STATEMENT,比如

image/svg+xml