关于java:mybatis源码笔记

19次阅读

共计 124484 个字符,预计需要花费 312 分钟才能阅读完成。

目录

  • 开撕 MyBatis 源码

    • 1. 手写长久层框架 -ipersistent

      • 1.1 JDBC 操作数据库 \_问题剖析
      • 1.2 JDBC 问题剖析 & 解决思路
      • 1.3 自定义长久层框架 \_思路剖析

        • 应用 JDBC 和应用长久层框架区别:
        • 框架,除了思考自身的工程设计,还须要思考到理论我的项目端的应用场景,干系方波及两端:
        • 外围接口 / 类重点阐明:
        • 我的项目应用端:
        • 自定义框架自身:
        • 最终手写的长久层框架结构参考:
      • 1.4 自定义长久层框架 \_编码
      • 1.5 自定义长久层框架 \_优化
    • 2. MyBatis 架构原理 & 次要组件

      • 2.1 MyBatis 的架构设计
      • 2.2 MyBatis 次要组件及其互相关系
    • 3. 源码分析 - 源码环境搭建

      • 3.1 源码环境搭建
      • 3.2 源码导入 & 编译
      • 3.3 编写测试代码

        • 3.3.1 配置 sqlMapConfig.xml
        • 3.3.2 配置 UserMapper.xml
        • 3.3.3 编写 User 类
        • 3.3.5 编写测试类
    • 4. 源码分析 - 初始化 \_如何解析的全局配置文件?

      • 前言
      • 解析配置文件源码流程:
      • 入口:SqlSessionFactoryBuilder#build
      • XMLConfigBuilder# 结构参数

        • 1. XpathParser# 构造函数
        • 1.1 XPathParser#createDocument
        • 2. XMLConfigBuilder# 构造函数
        • 2.1 Configuration# 构造函数
      • XMLConfigBuilder#parse

        • 1. XPathParser#evalNode(xpath 语法)
        • 2. XMLConfigBuilder#parseConfiguration(XNode)
    • 5. 源码分析 - 初始化 \_如何解析的映射配置文件?

      • 前言
      • 解析映射配置文件源码流程:
      • 入口:XMLConfigBuilder#mapperElement
      • \<package> 子标签

        • 1. Configuration#addMappers
        • 1.1 MapperRegistry#addMappers
        • 1.1.1 MapperAnnotationBuilder#parse
        • 1.1.1.1 MapperAnnotationBuilder#parseStatement
        • 1.1.1.1.2 MapperBuilderAssistant#addMappedStatement
      • \<mapper> 子标签

        • 1.XMLMapperBuilder# 构造函数
        • 1.1 XPathParser# 构造函数
        • 1.1.1 XPathParser#createDocument
        • 1.2 XMLMapperBuilder# 构造函数
        • 1.2.1MapperBuilderAssistant# 构造函数
        • 2. XMLMapperBuilder#parse
        • 2.1 XMLMapperBuilder#configurationElement
        • 2.1.1 XMLMapperBuilder#buildStatementFromContext
        • 2.1.1.1 XMLStatementBuilder# 构造函数
        • 2.1.1.2 XMLStatementBuilder#parseStatementNode
        • 2.1.1.2.1 MapperBuilderAssistant#addMappedStatement
        • 2.1.1.2.1.1 MappedStatement.Builder# 构造函数
        • 2.1.1.2.1.2 MappedStatement#build
    • 6. 源码分析 -SqlSource 创立流程

      • 相干类及对象
      • SqlSource 创立流程

        • 入口:XMLLanguageDriver#createSqlSource
        • XMLScriptBuilder# 构造函数
        • 1.XMLScriptBuilder#initNodeHandlerMap
        • XMLScriptBuilder#parseScriptNode
        • 1 XMLScriptBuilder#parseDynamicTags
        • 2. DynamicSqlSource# 构造函数
        • 3. RawSqlSource# 构造函数
        • 3.1 SqlSourceBuilder#parse
        • 3.1.1 ParameterMappingTokenHandler# 构造函数
        • 3.1.2 GenericTokenParser# 构造函数
        • 3.1.3 GenericTokenParser#parse
        • 3.1.4 StaticSqlSource# 构造函数
    • 7. 源码分析 - 揭秘 SqlSession 执行主流程

      • 7.1 相干类与接口
      • 7.2 流程剖析
      • 入口:DefaultSqlSession#selectList

        • 1. CachingExecutor#query
        • 2. BaseExecutor#query
        • 3. BaseExecutor#queryFromDatabase
        • 4. SimpleExecutor#doQuery
        • 4.1 Configuration#newStatementHandler
        • 4.1.1 RoutingStatementHandler# 构造函数
        • 4.2 SimpleExecutor#prepareStatement
        • 4.2.1 BaseExecutor#getConnection
        • 4.2.2 BaseStatementHandler#prepare
        • 4.2.2.1 PreparedStatementHandler#instantiateStatement
        • 4.2.3 PreparedStatementHandler#parameterize
        • 4.3 PreparedStatementHandler#query
        • 4.3.1 PreparedStatement#execute
        • 4.3.2 DefaultResultSetHandler#handleResultSets
        • 执行 sqlsession:参数有两个(statementId 和参数对象)
    • 8. 源码分析 - 揭秘如何设置的参数?

      • 入口:PreparedStatementHandler#parameterize 办法

        • DefaultParameterHandler#setParameters
        • BaseTypeHandler#setParameter
        • xxxTypeHandler#setNonNullParameter
    • 9. 源码分析 - 后果集映射流程

      • 入口:DefaultResultSetHandler#handleResultSets

        • DefaultResultSetHandler#handleRowValues
        • DefaultResultSetHandler#handleRowValuesForSimpleResultMap
        • 1. DefaultResultSetHandler#getRowValue
        • 1.1 DefaultResultSetHandler#createResultObject
        • 1.2 DefaultResultSetHandler#applyAutomaticMappings
        • 1.3 DefaultResultSetHandler#applyPropertyMappings
    • 10. 源码分析 - 获取 Mapper 代理对象流程

      • 入口:DefaultSqlSession#getMapper

        • Configuration#getMapper
        • 1. MapperRegistry#getMapper
        • 1.1 MapperProxyFactory#newInstance
    • 11. 源码分析 -invoke 办法

      • 入口:MapperProxy#invoke

        • MapperMethod
    • 12. 源码分析 - 插件机制

      • 12.1 插件概述
      • 12.2 Mybatis 插件介绍

        • 能干什么?
        • 如何自定义插件?
      • 12.3 自定义插件

        • 核心思想:
      • 12.4 源码剖析 - 插件

        • 插件配置信息的加载
        • 代理对象的生成
        • 拦挡逻辑的执行
    • 13. 源码分析 - 缓存策略

      • 一级缓存

        • 一级缓存原理探索与源码剖析
        • 1. 一级缓存 底层数据结构到底是什么?
        • 2. 一级缓存的执行流程
        • 一级缓存源码剖析论断:
      • 二级缓存

        • 启用二级缓存
        • 二级缓存源码剖析
        • 标签 < cache/> 的解析
        • buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)); 将 Cache 包装到 MappedStatement
        • 查问源码剖析
        • CachingExecutor
        • TransactionalCacheManager
        • TransactionalCache
        • 为何只有 SqlSession 提交或敞开之后?
        • 二级缓存的刷新
        • 总结:

开撕 MyBatis 源码

* 手写长久层框架 - 仿写 mybatis
* Mybatis 架构设计 & 次要组件
* Mybatis 如何实现的初始化?
* Mybatis 如何实现的 sql 解析及执行?
* Mybatis 如何设置的参数?
* Mybatis 如何进行的类型转换?
* Mybatis 如何封装的返回后果集?
* Mybatis 插件原理是什?
* Mybatis 缓存底层数据结构是什么?

1. 手写长久层框架 -ipersistent

1.1 JDBC 操作数据库 \_问题剖析

JDBC API 容许应用程序拜访任何模式的表格数据,特地是存储在关系数据库中的数据

代码示例:

public static void main(String[] args) { 
      Connection connection = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
  try {
  // 加载数据库驱动
  Class.forName("com.mysql.jdbc.Driver");
  // 通过驱动治理类获取数据库链接
  connection =
DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding=utf-8", "root", "root");
  // 定义 sql 语句?示意占位符
  String sql = "select * from user where username = ?";
  // 获取预处理 statement
  preparedStatement = connection.prepareStatement(sql);
  // 设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的参数值 preparedStatement.setString(1, "tom");
  // 向数据库收回 sql 执行查问,查问出后果集
  resultSet = preparedStatement.executeQuery();
  // 遍历查问后果集
  while (resultSet.next()) {int id = resultSet.getInt("id");
    String username = resultSet.getString("username");
  // 封装 User
    user.setId(id);
    user.setUsername(username);
  }
    System.out.println(user);
  }
  } catch (Exception e) {e.printStackTrace();
  } finally {
      // 开释资源
        if (resultSet != null) {
            try {resultSet.close();
            } catch (SQLException e) {e.printStackTrace();
            }
        }
        if (preparedStatement != null) {
            try {preparedStatement.close();
            } catch (SQLException e) {e.printStackTrace();
            }
        }
        if (connection != null) {
            try {connection.close();
            } catch (SQLException e) {e.printStackTrace();
            }
        }
  }
}

1.2 JDBC 问题剖析 & 解决思路

剖开代码,一一剖析:

(1)加载驱动,获取链接:

  • 存在问题 1:数据库配置信息存在 硬编码 问题。

    优化思路:应用配置文件!

  • 存在问题 2:频繁创立、开释 数据库连贯 问题。

    优化思路:应用数据连接池!

(2)定义 sql、设置参数、执行查问:

  • 存在问题 3:SQL 语句、设置参数、获取后果集参数均存在 硬编码 问题。

    优化思路:应用配置文件!

(2)遍历查问后果集:

  • 存在问题 4:手动封装 返回后果集,较为繁琐

    优化思路:应用 Java 反射、内省!

针对 JDBC 各个环节中存在的有余,当初,咱们整顿出对应的优化思路,对立汇总:

存在问题 优化思路
数据库配置信息存在硬编码问题 应用配置文件
频繁创立、开释数据库连贯问题 应用数据连接池
SQL 语句、设置参数、获取后果集参数均存在硬编码问题 应用配置文件
手动封装返回后果集,较为繁琐 应用 Java 反射、内省

1.3 自定义长久层框架 \_思路剖析

JDBC 是集体作战,凡事亲力亲为,低效而高险,本人加载驱动,本人建连贯,本人 …

而长久层框架好比是多工种合作,分工明确,执行高效,有专门负责解析注册驱动建设连贯的,有专门治理数据连接池的,有专门执行 sql 语句的,有专门做预处理参数的,有专门拆卸后果集的 …

优化思路:框架的作用,就是为了帮忙咱们减去沉重开发细节与冗余代码,使咱们能更加专一于业务利用开发。

应用 JDBC 和应用长久层框架区别:

是不是发现,领有这么一套长久层框架是如此舒服,咱们仅仅须要干两件事:

  • 配置数据源(地址 / 数据名 / 用户名 / 明码)
  • 编写 SQL 与参数筹备(SQL 语句 / 参数类型 / 返回值类型)
框架,除了思考自身的工程设计,还须要思考到理论我的项目端的应用场景,干系方波及两端:
  • 应用端(理论我的项目)
  • 长久层框架自身

以上两步,咱们通过一张架构图《手写长久层框架基本思路》来梳理分明:

外围接口 / 类重点阐明:
分工协作 角色定位 类名定义
负责读取配置文件 资源辅助类 Resources
负责存储数据库连贯信息 数据库资源类 Configuration
负责存储 SQL 映射定义、存储后果集映射定义 SQL 与后果集资源类 MappedStatement
负责解析配置文件,创立会话工厂 SqlSessionFactory 会话工厂构建者 SqlSessionFactoryBuilder
负责创立会话 SqlSession 会话工厂 SqlSessionFactory
指派执行器 Executor 会话 SqlSession
负责执行 SQL(配合指定资源 Mapped Statement) 执行器 Executor

失常来说我的项目只对应一套数据库环境,个别对应一个 SqlSessionFactory 实例对象,咱们应用单例模式只创立一个 SqlSessionFactory 实例。
如果须要配置多套数据库环境,那须要做一些拓展,例如 Mybatis 中通过 environments 等配置就能够反对多套测试 / 生产数据库环境进行切换。

我的项目应用端:

(1)调用框架 API,除了引入自定义长久层框架的 jar 包

(2)提供两局部配置信息:1.sqlMapConfig.xml : 数据库配置信息(地址 / 数据名 / 用户名 / 明码),以及 mapper.xml 的全门路

​ 2.mapper.xml : SQL 配置信息,寄存 SQL 语句、参数类型、返回值类型相干信息

自定义框架自身:

1、加载配置文件:依据配置文件的门路,加载配置文件成字节输出流,存储在内存中。

2、创立两个 javaBean(容器对象):寄存配置文件解析进去的内容

3、解析配置文件(应用 dom4j),并创立 SqlSession 会话对象

4、创立 SqlSessionFactory 接口以及实现类 DefaultSqlSessionFactory

5、创立 SqlSession 接口以及实现类 DefaultSqlSession

6、创立 Executor 接口以及实现类 SimpleExecutor

根本过程咱们曾经清晰,咱们再细化一下类图,更好的助于咱们理论编码:

最终手写的长久层框架结构参考:

1.4 自定义长久层框架 \_编码

  <properties>
        <!-- Encoding -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>11</java.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

  <!-- 引入 ipersistent 的依赖 >

在应用端我的项目中创立配置配置文件

创立 sqlMapConfig.xml

<configuration> 
    <!--1. 配置数据库配置信息 -->
    <dataSource>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///zdy_mybatis?useSSL=false&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </dataSource>


    <!--2. 引入映射配置文件 -->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"></mapper>
    </mappers>

</configuration> 

mapper.xml

<mapper namespace="User">
    
    <!-- 依据条件查问单个 -->
    <select id="selectOne" resultType="com.itheima.pojo.User" parameterType="com.itheima.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>

  
  <!-- 查问所有 -->
    <select id="selectList" resultType="com.itheima.pojo.User">
        select * from user
    </select>
</mapper>

User 实体

public class User {
  // 主键标识
  private Integer id;
  // 用户名
  private String username;
    
  public Integer getId() {return id;}
  public void setId(Integer id) {this.id = id;}
  public String getUsername() {return username;}
  public void setUsername(String username) {this.username = username;}

    @Override
  public String toString() {
    return "User{" +
    "id=" + id +
    ", username='" + username + '\'' + '}';
  }
}

再创立一个 Maven 子工程并且导入须要用到的依赖坐标

  <properties>
        <!-- Encoding -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>11</java.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- mysql 依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <!--junit 依赖 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <!-- 作用域测试范畴 -->
            <scope>test</scope>
        </dependency>

        <!--dom4j 依赖 -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>

        <!--xpath 依赖 -->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>


        <!--druid 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>

        <!-- log 日志 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
       
    </dependencies>

Resources

public class Resources {

    /**
     * 依据配置文件的门路,加载成字节输出流,存到内存中
     * @param path
     * @return
     */
    public static InputStream getResourceAsSteam(String path){InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }

Configuration

/**
 * 寄存外围配置文件解析的内容
 */
public class Configuration {

    // 数据源对象
    private DataSource dataSource;

    // map : key :statementId  value : 封装好的 MappedStatement
    private Map<String,MappedStatement> mappedStatementMap = new HashMap<>();

    public DataSource getDataSource() {return dataSource;}

    public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}

    public Map<String, MappedStatement> getMappedStatementMap() {return mappedStatementMap;}

    public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {this.mappedStatementMap = mappedStatementMap;}
}

MappedStatement

/**
 *  寄存解析映射配置文件的内容
 *     <select id="selectOne" resultType="com.itheima.pojo.User" parameterType="com.itheima.pojo.User">
 *         select * from user where id = #{id} and username = #{username}
 *     </select>
 */
public class MappedStatement {

    // 1. 惟一标识(statementId namespace.id)private String statementId;
    // 2. 返回后果类型
    private String resultType;
    // 3. 参数类型
    private String parameterType;
    // 4. 要执行的 sql 语句
    private String sql;

    // 5.mapper 代理:sqlcommandType
    private String sqlcommandType;

    public String getSqlcommandType() {return sqlcommandType;}

    public void setSqlcommandType(String sqlcommandType) {this.sqlcommandType = sqlcommandType;}

    public String getStatementId() {return statementId;}

    public void setStatementId(String statementId) {this.statementId = statementId;}

    public String getResultType() {return resultType;}

    public void setResultType(String resultType) {this.resultType = resultType;}

    public String getParameterType() {return parameterType;}

    public void setParameterType(String parameterType) {this.parameterType = parameterType;}

    public String getSql() {return sql;}

    public void setSql(String sql) {this.sql = sql;}
}

SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {

    /**
     * 1. 解析配置文件,封装 Configuration 2. 创立 SqlSessionFactory 工厂对象
     * @return
     */
    public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
        // 1. 解析配置文件,封装 Configuration
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parse(inputStream);

        SqlSessionFactory defatultSqlSessionFactory = new DefatultSqlSessionFactory(configuration);
        return  defatultSqlSessionFactory;
    }

}

XMLConfigerBuilder

public class XMLConfigBuilder {
    
    private Configuration configuration;

    public XMLConfigBuilder() {configuration = new Configuration();
    }

    /**
     * 应用 dom4j 解析 xml 文件,封装 configuration 对象
     * @param inputStream
     * @return
     */
    public Configuration parse(InputStream inputStream) throws DocumentException {Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();

        // 解析外围配置文件中数据源局部
        List<Element> list = rootElement.selectNodes("//property");
        //  <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>

        Properties properties = new Properties();
        for (Element element : list) {String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        }

        // 创立数据源对象(连接池)DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(properties.getProperty("driverClassName"));
        druidDataSource.setUrl(properties.getProperty("url"));
        druidDataSource.setUsername(properties.getProperty("username"));
        druidDataSource.setPassword(properties.getProperty("password"));

        // 创立好的数据源对象封装进 configuration 中、configuration.setDataSource(druidDataSource);


        // 解析映射配置文件
        // 1. 获取映射配置文件的门路  2. 解析  3. 封装好 mappedStatement
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {String mapperPath = element.attributeValue("resource");
            InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsSteam);
        }

        return configuration;
    }
}

XMLMapperBuilder

public class XMLMapperBuilder {
 private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {this.configuration = configuration;}

    public void parse(InputStream inputStream) throws DocumentException, ClassNotFoundException {Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();

        String namespace = rootElement.attributeValue("namespace");
        List<Element> select = rootElement.selectNodes("select");
        for (Element element : select) { //id 的值
            String id = element.attributeValue("id");
            String paramterType = element.attributeValue("paramterType");
            String resultType = element.attributeValue("resultType"); // 输出参数 class
            Class<?> paramterTypeClass = getClassType(paramterType);
            // 返回后果 class
            Class<?> resultTypeClass = getClassType(resultType);
            //statementId
            String key = namespace + "." + id;
            //sql 语句
            String textTrim = element.getTextTrim();
            // 封装 mappedStatement
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setParamterType(paramterTypeClass);
            mappedStatement.setResultType(resultTypeClass);
            mappedStatement.setSql(textTrim);
            // 填充 configuration
            configuration.getMappedStatementMap().put(key, mappedStatement); 

            private Class<?> getClassType (String paramterType) throws ClassNotFoundException {Class<?> aClass = Class.forName(paramterType);
                return aClass;
    }
}

sqlSessionFactory 接口及 D efaultSqlSessionFactory 实现类

public interface SqlSessionFactory {

    /**
     * 生产 sqlSession:封装着与数据库交互的办法
     * @return
     */
    public SqlSession openSession();}

public class DefatultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

    public DefatultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}

    @Override
    public SqlSession openSession() {

        // 执行器创立进去
        Executor executor = new SimpleExecutor();

        DefaultSqlSession defaultSqlSession = new DefaultSqlSession(configuration,executor);
        return defaultSqlSession;
    }
}

sqlSession 接口及 DefaultSqlSession 实现类

public interface SqlSession {

    /**
     * 查问所有的办法 select * from user where username like '%aaa%' and sex = ''
     * 参数 1:惟一标识
     * 参数 2:入参
     */
    public <E> List<E> selectList(String statementId,Object parameter) throws Exception;


    /**
     * 查问单个的办法
     */
    public <T> T selectOne(String statementId,Object parameter) throws Exception;
}

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    private Executor executor;

    public DefaultSqlSession(Configuration configuration, Executor executor) {
        this.configuration = configuration;
        this.executor = executor;
    }

    @Override                    // user.selectList      1 tom user
    public <E> List<E> selectList(String statementId, Object params) throws Exception {MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        // 将查问操作委派给底层的执行器
        List<E> list = executor.query(configuration,mappedStatement,params);

        return list;
    }

    @Override
    public <T> T selectOne(String statementId, Object params) throws Exception {List<Object> list = this.selectList(statementId, params);
        if(list.size() == 1){return (T) list.get(0);
        }else if(list.size() > 1){throw new RuntimeException("返回后果过多");
        }else {return null;}
    }
}   

Executor

public interface Executor {<E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object params) throws Exception;
}

SimpleExecutor

public class SimpleExecutor implements Executor {

    /**
     * 执行 JDBC 操作
     * @param configuration
     * @param mappedStatement
     * @param params
     * @param <E>
     * @return
     */
    @Override                                                                               // user product
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object params) throws Exception {

        // 1. 加载驱动,获取连贯
        Connection connection = configuration.getDataSource().getConnection();

        // 2. 获取 prepareStatement 预编译对象
        /*
             select * from user where id = #{id} and username = #{username}
             select * from user where id = ? and username = ?
             占位符替换:#{}替换成?留神:#{id}外面的 id 名称要保留
         */
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSQL(sql);
        String finaLSql = boundSql.getFinaLSql();
        PreparedStatement preparedStatement = connection.prepareStatement(finaLSql);

        // 3. 设置参数

        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if(parameterMappings.size() > 0){
        // com.itheima.pojo.User
        String parameterType = mappedStatement.getParameterType();
        Class<?> parameterTypeClass = Class.forName(parameterType);

        for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);
            // id
            String content = parameterMapping.getContent();
            // 反射
            Field declaredField = parameterTypeClass.getDeclaredField(content);
            // 暴力拜访
            declaredField.setAccessible(true);
            Object value = declaredField.get(params);
            preparedStatement.setObject(i+1 ,value);
        }
        }

        // 4. 执行 sql, 发动查问
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = Class.forName(resultType);

        ArrayList<E> list = new ArrayList<>();
        // 5. 遍历封装
        while (resultSet.next()){
            // 元数据信息中蕴含了字段名 字段的值
            ResultSetMetaData metaData = resultSet.getMetaData();
            Object obj = resultTypeClass.newInstance();
            for (int i = 1; i <= metaData.getColumnCount() ; i++) {

                // id  username
                String columnName = metaData.getColumnName(i);
                Object value = resultSet.getObject(columnName);
                // 属性形容器
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(obj,value);
            }
            list.add((E) obj);
        }
        return list;
    }

    /**
     *  1. 将 sql 中 #{}替换成?2. 将 #{}外面的值保留
     * @param sql
     * @return
     */
    private BoundSql getBoundSQL(String sql) {
        // 标记处理器:配合标记解析器实现标记的解析工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();

        // 标记解析器
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        String finalSql = genericTokenParser.parse(sql);

        // #{}外面的值的汇合
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        BoundSql boundSql = new BoundSql(finalSql, parameterMappings);

        return boundSql;
    }
}

BoundSql

public class BoundSql {
   // 解析过后的 sql 语句
    private String sqlText;
    // 解析进去的参数
    private List<ParameterMapping> parameterMappingList = new ArrayList<ParameterMapping>();

    public BoundSql(String sqlText, List<ParameterMapping>
            parameterMappingList) {
        this.sqlText = sqlText;
        this.parameterMappingList = parameterMappingList;
    }

    public String getSqlText() {return sqlText;}

    public void setSqlText(String sqlText) {this.sqlText = sqlText;}

    public List<ParameterMapping> getParameterMappingList() {return parameterMappingList;}

    public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {this.parameterMappingList = parameterMappingList;}
}

1.5 自定义长久层框架 \_优化

通过上述咱们的自定义框架,咱们解决了 JDBC 操作数据库带来的一些问题:例如频繁创立开释数据库连 接,硬编码,手动封装返回后果集等问题,然而当初咱们持续来剖析刚刚实现的自定义框架代码,有没 有什么问题?

问题如下:

  • dao 的实现类中存在反复的代码,整个操作的过程模板反复(创立 sqlsession, 调用 sqlsession 方 法,敞开 sqlsession)
  • dao 的实现类中存在硬编码,调用 sqlsession 的办法时,参数 statement 的 id 硬编码

解决:应用代理模式来创立接口的代理对象

  @Test
    public void test2() throws Exception {InputStream resourceAsSteam = Resources.getResourceAsSteam(path:"sqlMapConfig.xml")
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        SqlSession sqlSession = build.openSession();
        User user = new User();
        user.setld(l);
        user.setUsername("tom");
        // 代理对象
        UserMapper userMapper = sqlSession.getMappper(UserMapper.class);
        User userl = userMapper.selectOne(user);
        System ・ out.println(userl);
    }

在 sqlSession 中增加办法

public interface SqlSession {public <T> T getMappper(Class<?> mapperClass);

实现类

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    private Executor executor;

    public DefaultSqlSession(Configuration configuration, Executor executor) {
        this.configuration = configuration;
        this.executor = executor;
    }

    @Override
    public <T> T getMapper(Class<?> c) {

        // 基于 JDK 动静代理产生接口的代理对象
        Object proxy = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{c}, new InvocationHandler() {

            /*
             o : 代理对象:很少用到
             method:正在执行的办法
             objects:办法的参数
             */
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                // findByCondition
                String methodName = method.getName();

                // com.itheima.dao.IUserDao
                String className = method.getDeclaringClass().getName();

                // 惟一标识:namespace.id  com.itheima.dao.IUserDao.findByCondition
                String statementId = className + "." +methodName;

                MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
                String sql = mappedStatement.getSql();

                // sqlcommandType select  insert update  delete
                String sqlcommandType = mappedStatement.getSqlcommandType();
                switch (sqlcommandType){

                    case "select":
                        // 查问操作 问题来了:selectList 还是 selectOne?
                        Type genericReturnType = method.getGenericReturnType();
                        // 判断是否实现泛型类型参数化
                        if(genericReturnType instanceof ParameterizedType){return selectList(statementId,objects);
                        }

                             return selectOne(statementId,objects);

                    case "update":
                        break;
                        // 更新操作
                    case "delete":
                        break;
                        // 删除操作
                    case "insert":
                        break;
                        // 增加操作
                }

                return null;
            }
        });


        return (T) proxy;
    }

2. MyBatis 架构原理 & 次要组件

2.1 MyBatis 的架构设计

mybatis 架构四层作用是什么呢?

  • Api 接口层:提供 API 减少、删除、批改、查问等接口,通过 API 接口对数据库进行操作。
  • 数据处理层:次要负责 SQL 的 查问、解析、执行以及后果映射的解决,次要作用解析 sql 依据调用申请实现一次数据库操作.
  • 框架撑持层:负责通用根底服务撑持, 蕴含事务管理、连接池治理、缓存治理等共用组件的封装,为下层提供根底服务撑持.
  • 疏导层:疏导层是配置和启动 MyBatis 配置信息的形式

2.2 MyBatis 次要组件及其互相关系

组件关系如下图所示:

组件介绍:

  • SqlSession:是 Mybatis 对外裸露的外围 API,提供了对数据库的 DRUD 操作接口。
  • Executor:执行器,由 SqlSession 调用,负责数据库操作以及 Mybatis 两级缓存的保护
  • StatementHandler:封装了 JDBC Statement 操作,负责对 Statement 的操作,例如 PrepareStatement 参数的设置以及后果集的解决。
  • ParameterHandler:是 StatementHandler 外部一个组件,次要负责对 ParameterStatement 参数的设置
  • ResultSetHandler:是 StatementHandler 外部一个组件,次要负责对 ResultSet 后果集的解决,封装成指标对象返回
  • TypeHandler:用于 Java 类型与 JDBC 类型之间的数据转换,ParameterHandler 和 ResultSetHandler 会别离应用到它的类型转换性能
  • MappedStatement:是对 Mapper 配置文件或 Mapper 接口办法上通过注解申明 SQL 的封装
  • Configuration:Mybatis 所有配置都对立由 Configuration 进行治理,外部由具体对象别离治理各自的小功能模块

3. 源码分析 - 源码环境搭建

3.1 源码环境搭建

  • mybatis 源码地址:https://github.com/mybatis/mybatis-3

3.2 源码导入 & 编译

3.3 编写测试代码

3.3.1 配置 sqlMapConfig.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">
            <!-- 应用 jdbc 事务管理 -->
            <transactionManager type="JDBC" />
            <!-- 数据库连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url"
                          value="jdbc:mysql:///zdy_mybatis?useSSL=false&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>

   <!-- 第二局部:引入映射配置文件 -->
    <mappers>
      <mapper resource="mapper/UserMapper.xml"></mapper>
    </mappers>
    
</configuration>
3.3.2 配置 UserMapper.xml
<?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="user">
 
  <select id="findUserById" parameterType="int" resultType="com.itheima.pojo.User">
        SELECT id,username FROM  user WHERE id = #{id}
    </select>
  
</mapper>
3.3.3 编写 User 类
package com.itheima.pojo;

import lombok.Data;

@Data
public class User {

    // ID 标识
    private Integer id;
    // 用户名
    private String username;

}
3.3.5 编写测试类
public class MybatisTest {

      @Test
      public void test1() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        User user = sqlSession.selectOne("user.findUserById", user1);
        System.out.println(user);
        System.out.println("MyBatis 源码环境搭建胜利...");
        sqlSession.close();}

}

输入:

4. 源码分析 - 初始化 \_如何解析的全局配置文件?

前言

全局配置文件可配置参数:https://mybatis.org/mybatis-3/zh/configuration.html

  • Configuration 对象
public class Configuration {

  protected Environment environment;

  // 容许在嵌套语句中应用分页(RowBounds)。如果容许应用则设置为 false。默认为 false
  protected boolean safeRowBoundsEnabled;
  // 容许在嵌套语句中应用分页(ResultHandler)。如果容许应用则设置为 false
  protected boolean safeResultHandlerEnabled = true;
  // 是否开启主动驼峰命名规定(camel case)映射,即从经典数据库列名 A_COLUMN
  // 到经典 Java 属性名 aColumn 的相似映射。默认 false
  protected boolean mapUnderscoreToCamelCase;
  // 当开启时,任何办法的调用都会加载该对象的所有属性。否则,每个属性会按需加载。默认值 false (true in ≤3.4.1)
  protected boolean aggressiveLazyLoading;
  // 是否容许繁多语句返回多后果集(须要兼容驱动)。protected boolean multipleResultSetsEnabled = true;
  // 容许 JDBC 反对主动生成主键,须要驱动兼容。这就是 insert 时获取 mysql 自增主键 /oracle sequence 的开关。// 注:一般来说, 这是心愿的后果, 应该默认值为 true 比拟适合。protected boolean useGeneratedKeys;
  // 应用列标签代替列名, 一般来说, 这是心愿的后果
  protected boolean useColumnLabel = true;
  // 是否启用缓存
  protected boolean cacheEnabled = true;
  // 指定当后果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)办法,// 这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。protected boolean callSettersOnNulls;
  // 容许应用办法签名中的名称作为语句参数名称。为了应用该个性,你的工程必须采纳 Java 8 编译,// 并且加上 -parameters 选项。(从 3.4.1 开始)protected boolean useActualParamName = true;
  // 当返回行的所有列都是空时,MyBatis 默认返回 null。当开启这个设置时,MyBatis 会返回一个空实例。// 请留神,它也实用于嵌套的后果集 (i.e. collectioin and association)。(从 3.4.2 开始)// 注:这里应该拆分为两个参数比拟适合, 一个用于后果集,一个用于单记录。// 通常来说,咱们会心愿后果集不是 null,单记录依然是 null
  protected boolean returnInstanceForEmptyRow;

  protected boolean shrinkWhitespacesInSql;

  // 指定 MyBatis 减少到日志名称的前缀。protected String logPrefix;
  // 指定 MyBatis 所用日志的具体实现,未指定时将主动查找。个别倡议指定为 slf4j 或 log4j
  protected Class<? extends Log> logImpl;
  // 指定 VFS 的实现, VFS 是 mybatis 提供的用于拜访 AS 内资源的一个简便接口
  protected Class<? extends VFS> vfsImpl;
  protected Class<?> defaultSqlProviderType;
  // MyBatis 利用本地缓存机制(Local Cache)避免循环援用(circular references)和减速反复嵌套查问。// 默认值为 SESSION,这种状况下会缓存一个会话中执行的所有查问。// 若设置值为 STATEMENT,本地会话仅用在语句执行上,对雷同 SqlSession 的不同调用将不会共享数据。protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  // 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。某些驱动须要指定列的 JDBC 类型,// 少数状况间接用个别类型即可,比方 NULL、VARCHAR 或 OTHER。protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  // 指定对象的哪个办法触发一次提早加载。protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
  // 设置超时工夫,它决定驱动期待数据库响应的秒数。默认不超时
  protected Integer defaultStatementTimeout;
  // 为驱动的后果集设置默认获取数量。protected Integer defaultFetchSize;
  // SIMPLE 就是一般的执行器;REUSE 执行器会重用预处理语句(prepared statements);// BATCH 执行器将重用语句并执行批量更新。protected ResultSetType defaultResultSetType;

  // 默认执行器类型
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  // 指定 MyBatis 应如何主动映射列到字段或属性。// NONE 示意勾销主动映射;// PARTIAL 只会主动映射没有定义嵌套后果集映射的后果集。// FULL 会主动映射任意简单的后果集(无论是否嵌套)。protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  // 指定发现主动映射指标未知列(或者未知属性类型)的行为。这个值应该设置为 WARNING 比拟适合
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
  // settings 下的 properties 属性
  protected Properties variables = new Properties();
  // 默认的反射器工厂, 用于操作属性、结构器不便
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  // 对象工厂, 所有的类 resultMap 类都须要依赖于对象工厂来实例化
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  // 对象包装器工厂, 次要用来在创立非原生对象, 比方减少了某些监控或者非凡属性的代理类
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  // 提早加载的全局开关。当开启时,所有关联对象都会提早加载。特定关联关系中可通过设置 fetchType 属性来笼罩该项的开关状态
  protected boolean lazyLoadingEnabled = false;
  // 指定 Mybatis 创立具备提早加载能力的对象所用到的代理工具。MyBatis 3.3+ 应用 JAVASSIST
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  // MyBatis 能够依据不同的数据库厂商执行不同的语句,这种多厂商的反对是基于映射语句中的 databaseId 属性。protected String databaseId;
  /**
   * Configuration factory class.
   * Used to create Configuration for loading deserialized unread properties.
   *
   * @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
   */
  protected Class<?> configurationFactory;

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

  // mybatis 插件列表
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);

  // 类型注册器, 用于在执行 sql 语句的出入参映射以及 mybatis-config 文件里的各种配置
  // 比方 <transactionManager type="JDBC"/><dataSource type="POOLED"> 时应用简写
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check" + savedValue.getResource() + "and" + targetValue.getResource());
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

  protected final Set<String> loadedResources = new HashSet<>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

  /*
   * A map holds cache-ref relationship. The key is the namespace that
   * references a cache bound to another namespace and the value is the
   * namespace which the actual cache is bound to.
   */
  protected final Map<String, String> cacheRefMap = new HashMap<>();

  public Configuration(Environment environment) {this();
    this.environment = environment;
  }

问题:外围配置文件 & 映射配置文件如何被解析的?

解析配置文件源码流程:

入口:SqlSessionFactoryBuilder#build

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // XMLConfigBuilder: 用来解析 XML 配置文件
      // 应用构建者模式
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // parser.parse():应用 XPATH 解析 XML 配置文件,将配置文件封装为 Configuration 对象
      // 返回 DefaultSqlSessionFactory 对象,该对象领有 Configuration 对象(封装配置文件信息)return build(parser.parse());
    } catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {ErrorContext.instance().reset();
      try {inputStream.close();
      } catch (IOException e) {// Intentionally ignore. Prefer previous error.}
    }
  }

创立 XMLConfigBuilder 对象,这个类是 BaseBuilder 的子类,BaseBuilder 类图:

看到这些子类基本上都是以 Builder 结尾,这里应用的是Builder 建造者设计模式

XMLConfigBuilder# 结构参数

XMLConfigBuilder: 用来解析 XML 配置文件(应用构建者模式)

// XMLConfigBuilder: 用来解析 XML 配置文件
// 应用构建者模式
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
 public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);

Mybatis 对应解析包 org.apache.ibatis.parsing:

XPathParser 基于 Java XPath 解析器,用于解析 MyBatis 中

  • SqlMapConfig.xml
  • mapper.xml

XPathParser 次要内容:

1. XpathParser# 构造函数

用来应用 XPath 语法解析 XML 的解析器

public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {commonConstructor(validation, variables, entityResolver);
    // 解析 XML 文档为 Document 对象
    this.document = createDocument(new InputSource(inputStream));
  }
1.1 XPathParser#createDocument

解析全局配置文件,封装为 Document 对象(封装一些子节点,应用 XPath 语法解析获取)

private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      // 进行 dtd 或者 Schema 校验
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      // 设置疏忽正文为 true
      factory.setIgnoringComments(true);
      // 设置是否疏忽元素内容中的空白
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {throw exception;}

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {throw exception;}

        @Override
        public void warning(SAXParseException exception) throws SAXException {}});
      // 通过 dom 解析,获取 Document 对象
      return builder.parse(inputSource);
    } catch (Exception e) {throw new BuilderException("Error creating document instance.  Cause:" + e, e);
    }
  }
2. XMLConfigBuilder# 构造函数

创立 Configuration 对象,同时初始化内置类的别名

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //  创立 Configuration 对象,并通过 TypeAliasRegistry 注册一些 Mybatis 外部相干类的别名
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
2.1 Configuration# 构造函数

创立 Configuration 对象,同时初始化内置类的别名

public Configuration() {
        //TypeAliasRegistry(类型别名注册器)typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

        typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
        typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
        typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

        typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
        typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
        typeAliasRegistry.registerAlias("LRU", LruCache.class);
        typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
        typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

        typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

        typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
        typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

        typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
        typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
        typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
        typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
        typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
        typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
        typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

        typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
        typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

        languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
        languageRegistry.register(RawLanguageDriver.class);
    }

XMLConfigBuilder#parse

// 应用 XPATH 解析 XML 配置文件,将配置文件封装为 Configuration 对象
parser.parse();

XMLConfigBuilder#parse

解析 XML 配置文件

/**
   * 解析 XML 配置文件
   * @return
   */
  public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // parser.evalNode("/configuration"):通过 XPATH 解析器,解析 configuration 根节点
    // 从 configuration 根节点开始解析,最终将解析出的内容封装到 Configuration 对象中
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
1. XPathParser#evalNode(xpath 语法)

XPath 解析器,专门用来通过 Xpath 语法解析 XML 返回 XNode 节点

public XNode evalNode(String expression) {
    // 依据 XPATH 语法,获取指定节点
    return evalNode(document, expression);
  }

  public XNode evalNode(Object root, String expression) {Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    if (node == null) {return null;}
    return new XNode(this, node, variables);
  }
2. XMLConfigBuilder#parseConfiguration(XNode)

从 configuration 根节点开始解析,最终将解析出的内容封装到 Configuration 对象中

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // 解析 </properties> 标签
      propertiesElement(root.evalNode("properties"));
      // 解析 </settings> 标签
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 解析 </typeAliases> 标签
      typeAliasesElement(root.evalNode("typeAliases"));
      // 解析 </plugins> 标签
      pluginElement(root.evalNode("plugins"));
      // 解析 </objectFactory> 标签
      objectFactoryElement(root.evalNode("objectFactory"));
      // 解析 </objectWrapperFactory> 标签
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 解析 </reflectorFactory> 标签
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      
      // read it after objectFactory and objectWrapperFactory issue #631
      // 解析 </environments> 标签
      environmentsElement(root.evalNode("environments"));
      // 解析 </databaseIdProvider> 标签
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 解析 </typeHandlers> 标签
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析 </mappers> 标签 加载映射文件流程主入口
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause:" + e, e);
    }
  }
### SqlSessionFactoryBuilder#build

返回 DefaultSqlSessionFactory 对象,该对象领有 Configuration 对象(封装配置文件信息)

// 返回 DefaultSqlSessionFactory 对象,该对象领有 Configuration 对象(封装配置文件信息)return build(parser.parse());
public SqlSessionFactory build(Configuration config) {
    // 创立 SqlSessionFactory 接口的默认实现类
    return new DefaultSqlSessionFactory(config);
  }

总结

5. 源码分析 - 初始化 \_如何解析的映射配置文件?

前言

### select

select 元素容许你配置很多属性来配置每条语句的行为细节

<select
  id="select"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">
### insert, update 和 delete

数据变更语句 insert,update 和 delete 的实现十分靠近

<insert
  id="insert"
  parameterType="com.itheima.pojo.User"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""keyColumn=""
  useGeneratedKeys=""timeout="20">

<update
  id="update"
  parameterType="com.itheima.pojo.User"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<delete
  id="delete"
  parameterType="com.itheima.pojo.User"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
### 动静 sql

借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素品种

  • if
  • choose (when, otherwise)
    MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句
<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state =‘ACTIVE’<choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

问题:映射配置文件中标签和属性如何被解析封装的?

问题:sql 占位符如何进行的替换?动静 sql 如何进行的解析?

解析映射配置文件源码流程:

入口:XMLConfigBuilder#mapperElement

解析全局配置文件中的

标签

/**
   * 解析 <mappers> 标签
   * @param parent  mappers 标签对应的 XNode 对象
   * @throws Exception
   */
  private void mapperElement(XNode parent) throws Exception {if (parent != null) {
      // 获取 <mappers> 标签的子标签
      for (XNode child : parent.getChildren()) {
        // <package> 子标签
        if ("package".equals(child.getName())) {
          // 获取 mapper 接口和 mapper 映射文件对应的 package 包名
          String mapperPackage = child.getStringAttribute("name");
          // 将包下所有的 mapper 接口以及它的代理对象存储到一个 Map 汇合中,key 为 mapper 接口类型,value 为代理对象工厂
          configuration.addMappers(mapperPackage);
        } else {// <mapper> 子标签
          // 获取 <mapper> 子标签的 resource 属性
          String resource = child.getStringAttribute("resource");
          // 获取 <mapper> 子标签的 url 属性
          String url = child.getStringAttribute("url");
          // 获取 <mapper> 子标签的 class 属性
          String mapperClass = child.getStringAttribute("class");
          // 它们是互斥的
          if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 专门用来解析 mapper 映射文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 通过 XMLMapperBuilder 解析 mapper 映射文件
            mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            // 通过 XMLMapperBuilder 解析 mapper 映射文件
            mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);
            // 将指定 mapper 接口以及它的代理对象存储到一个 Map 汇合中,key 为 mapper 接口类型,value 为代理对象工厂
            configuration.addMapper(mapperInterface);
          } else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

\<package> 子标签

1. Configuration#addMappers

将包下所有的 mapper 接口以及它的代理对象存储到一个 Map 汇合中,key 为 mapper 接口类型,value 为代理对象工厂

public void addMappers(String packageName) {mapperRegistry.addMappers(packageName);
}
1.1 MapperRegistry#addMappers

将 Mapper 接口增加到 MapperRegistry 中

//1
public void addMappers(String packageName) {addMappers(packageName, Object.class);
}

//2
public void addMappers(String packageName, Class<?> superType) {ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    // 依据 package 名称,加载该包下 Mapper 接口文件(不是映射文件)resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    // 获取加载的 Mapper 接口
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      // 将 Mapper 接口增加到 MapperRegistry 中
      addMapper(mapperClass);
    }
  }

//3
public <T> void addMapper(Class<T> type) {if (type.isInterface()) {
      // 如果 Map 汇合中曾经有该 mapper 接口的映射,就不须要再存储了
      if (hasMapper(type)) {throw new BindingException("Type" + type + "is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // 将 mapper 接口以及它的代理对象存储到一个 Map 汇合中,key 为 mapper 接口类型,value 为代理对象工厂
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        
        // 用来解析注解形式的 mapper 接口
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        // 解析注解形式的 mapper 接口
        parser.parse();
        loadCompleted = true;
      } finally {if (!loadCompleted) {knownMappers.remove(type);
        }
      }
    }
  }
1.1.1 MapperAnnotationBuilder#parse

解析注解形式的 mapper 接口

public void parse() {
    // 获取 mapper 接口的全门路
    String resource = type.toString();
    // 是否解析过该 mapper 接口
    if (!configuration.isResourceLoaded(resource)) {
      // 先解析 mapper 映射文件
      loadXmlResource();
      // 设置解析标识
      configuration.addLoadedResource(resource);
      // Mapper 构建者助手
      assistant.setCurrentNamespace(type.getName());
      // 解析 CacheNamespace 注解
      parseCache();
      // 解析 CacheNamespaceRef 注解
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            // 每个 mapper 接口中的办法,都解析成 MappedStatement 对象
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    // 去查看所有的 incompleteMethods, 如果能够解析了. 那就移除
    parsePendingMethods();}
1.1.1.1 MapperAnnotationBuilder#parseStatement

每个 mapper 接口中的办法,都解析成 MappedStatement 对象

void parseStatement(Method method) {
    // 获取 Mapper 接口的形参类型
    Class<?> parameterTypeClass = getParameterType(method);
    // 解析 Lang 注解
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {Options options = method.getAnnotation(Options.class);
      // 组装 mappedStatementId
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = null;
      // 获取该 mapper 接口中的办法是 CRUD 操作的哪一种
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      // 是否是 SELECT 操作
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;

      // 主键生成器,用于主键返回
      KeyGenerator keyGenerator;
      String keyProperty = null;
      String keyColumn = null;
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();} else if (options == null) {keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        } else {keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();}
      } else {keyGenerator = NoKeyGenerator.INSTANCE;}

      if (options != null) {if (FlushCachePolicy.TRUE.equals(options.flushCache())) {flushCache = true;} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {flushCache = false;}
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        resultSetType = options.resultSetType();}

      // 解决 ResultMap 注解
      String resultMapId = null;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {String[] resultMaps = resultMapAnnotation.value();
        StringBuilder sb = new StringBuilder();
        for (String resultMap : resultMaps) {if (sb.length() > 0) {sb.append(",");
          }
          sb.append(resultMap);
        }
        resultMapId = sb.toString();} else if (isSelect) {resultMapId = parseResultMap(method);
      }

      // 通过 Mapper 构建助手,创立一个 MappedStatement 对象,封装信息
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }
1.1.1.1.2 MapperBuilderAssistant#addMappedStatement

通过 Mapper 构建助手,创立一个 MappedStatement 对象,封装信息

public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    // 利用构建者模式,去创立 MappedStatement.Builder, 用于创立 MappedStatement 对象
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);
    }

    // 通过 MappedStatement.Builder,构建一个 MappedStatement
    MappedStatement statement = statementBuilder.build();
    // 将 MappedStatement 对象存储到 Configuration 中的 Map 汇合中,key 为 statement 的 id,value 为 MappedStatement 对象
    configuration.addMappedStatement(statement);
    return statement;
  }

\<mapper> 子标签

1.XMLMapperBuilder# 构造函数

专门用来解析 mapper 映射文件

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }
1.1 XPathParser# 构造函数

用来应用 XPath 语法解析 XML 的解析器

public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {commonConstructor(validation, variables, entityResolver);
    // 解析 XML 文档为 Document 对象
    this.document = createDocument(new InputSource(inputStream));
  }
1.1.1 XPathParser#createDocument

创立 Mapper 映射文件对应的 Document 对象

private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      // 进行 dtd 或者 Schema 校验
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      // 设置疏忽正文为 true
      factory.setIgnoringComments(true);
      // 设置是否疏忽元素内容中的空白
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {throw exception;}

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {throw exception;}

        @Override
        public void warning(SAXParseException exception) throws SAXException {}});
      // 通过 dom 解析,获取 Document 对象
      return builder.parse(inputSource);
    } catch (Exception e) {throw new BuilderException("Error creating document instance.  Cause:" + e, e);
    }
  }
1.2 XMLMapperBuilder# 构造函数
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }
1.2.1MapperBuilderAssistant# 构造函数

用于构建 MappedStatement 对象的

public MapperBuilderAssistant(Configuration configuration, String resource) {super(configuration);
    ErrorContext.instance().resource(resource);
    this.resource = resource;
  }
2. XMLMapperBuilder#parse

通过 XMLMapperBuilder 解析 mapper 映射文件

public void parse() {
   // mapper 映射文件是否曾经加载过
   if (!configuration.isResourceLoaded(resource)) {
     // 从映射文件中的 <mapper> 根标签开始解析,直到残缺的解析结束
     configurationElement(parser.evalNode("/mapper"));
     // 标记曾经解析
     configuration.addLoadedResource(resource);
     bindMapperForNamespace();}

   parsePendingResultMaps();
   parsePendingCacheRefs();
   parsePendingStatements();}
2.1 XMLMapperBuilder#configurationElement

从映射文件中的

根标签开始解析,直到残缺的解析结束

 /**
   * 解析映射文件
   * @param context 映射文件根节点 <mapper> 对应的 XNode
   */
  private void configurationElement(XNode context) {
    try {
      // 获取 <mapper> 标签的 namespace 值,也就是命名空间
      String namespace = context.getStringAttribute("namespace");
      // 命名空间不能为空
      if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");
      }
      
      // 设置以后的命名空间为 namespace 的值
      builderAssistant.setCurrentNamespace(namespace);
      // 解析 <cache-ref> 子标签
      cacheRefElement(context.evalNode("cache-ref"));
      // 解析 <cache> 子标签
      cacheElement(context.evalNode("cache"));
      
      // 解析 <parameterMap> 子标签
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析 <resultMap> 子标签
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析 <sql> 子标签,也就是 SQL 片段
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析 <select>\<insert>\<update>\<delete> 子标签
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is'" + resource + "'. Cause:" + e, e);
    }
  }
2.1.1 XMLMapperBuilder#buildStatementFromContext

用来创立 MappedStatement 对象的

//1、构建 MappedStatement
private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());
    }
    // 构建 MappedStatement
    buildStatementFromContext(list, null);
  }

//2、专门用来解析 MappedStatement
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {
      // MappedStatement 解析器
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 解析 select 等 4 个标签,创立 MappedStatement 对象
        statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);
      }
    }
  }
2.1.1.1 XMLStatementBuilder# 构造函数

专门用来解析 MappedStatement

public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {super(configuration);
    this.builderAssistant = builderAssistant;
    this.context = context;
    this.requiredDatabaseId = databaseId;
  }
2.1.1.2 XMLStatementBuilder#parseStatementNode

解析

子标签

/**
   * 解析 <select>\<insert>\<update>\<delete> 子标签
   */
  public void parseStatementNode() {
    // 获取 statement 的 id 属性(特地要害的值)String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    // 获取入参类型
    String parameterType = context.getStringAttribute("parameterType");
    // 别名解决,获取入参对应的 Java 类型
    Class<?> parameterTypeClass = resolveClass(parameterType);
    // 获取 ResultMap
    String resultMap = context.getStringAttribute("resultMap");
    // 获取后果映射类型
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);
    
    // 别名解决,获取返回值对应的 Java 类型
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    
    // 设置默认 StatementType 为 Prepared,该参数指定了前面的 JDBC 解决时,采纳哪种 Statement
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    // 解析 SQL 命令类型是什么?确定操作是 CRUD 中的哪一种
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // 是否查问语句
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    // <include> 标签解析
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    // 解析 <selectKey> 标签
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    // 创立 SqlSource,解析 SQL,封装 SQL 语句(未参数绑定)和入参信息
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
   
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 通过构建者助手,创立 MappedStatement 对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
2.1.1.2.1 MapperBuilderAssistant#addMappedStatement

通过构建者助手,创立 MappedStatement 对象

public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    // 利用构建者模式,去创立 MappedStatement.Builder, 用于创立 MappedStatement 对象
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);
    }

    // 通过 MappedStatement.Builder,构建一个 MappedStatement
    MappedStatement statement = statementBuilder.build();
    // 将 MappedStatement 对象存储到 Configuration 中的 Map 汇合中,key 为 statement 的 id,value 为 MappedStatement 对象
    configuration.addMappedStatement(statement);
    return statement;
  }
2.1.1.2.1.1 MappedStatement.Builder# 构造函数

利用构建者模式,去创立 MappedStatement.Builder, 用于创立 MappedStatement 对象

public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
      mappedStatement.configuration = configuration;
      mappedStatement.id = id;
      mappedStatement.sqlSource = sqlSource;
      mappedStatement.statementType = StatementType.PREPARED;
      mappedStatement.resultSetType = ResultSetType.DEFAULT;
      mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<>()).build();
      mappedStatement.resultMaps = new ArrayList<>();
      mappedStatement.sqlCommandType = sqlCommandType;
      mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      String logId = id;
      if (configuration.getLogPrefix() != null) {logId = configuration.getLogPrefix() + id;
      }
      mappedStatement.statementLog = LogFactory.getLog(logId);
      mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();}
2.1.1.2.1.2 MappedStatement#build

通过 MappedStatement.Builder,构建一个 MappedStatement

public MappedStatement build() {
      assert mappedStatement.configuration != null;
      assert mappedStatement.id != null;
      assert mappedStatement.sqlSource != null;
      assert mappedStatement.lang != null;
      mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
      return mappedStatement;
    }

6. 源码分析 -SqlSource 创立流程

问题:sql 占位符如何进行的替换?动静 sql 如何进行的解析?

相干类及对象

  • XMLLanguageDriver
  • XMLScriptBuilder
  • SqlSource 接口
  • SqlSourceBuilder
  • DynamicSqlSource:次要是封装动静 SQL 标签解析之后的 SQL 语句和带有 &dollar;{}的 SQL 语句
  • RawSqlSource:次要封装带有 #{}的 SQL 语句
  • StaticSqlSource:是 BoundSql 中要存储 SQL 语句的一个载体,下面两个 SqlSource 的 SQL 语句,最终都会存储到该 SqlSource 实现类
<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = #{ACTIVE}
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

SqlSource 创立流程

入口:XMLLanguageDriver#createSqlSource

创立 SqlSource,解析 SQL,封装 SQL 语句(未参数绑定)和入参信息

@Override
    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
        // 初始化了动静 SQL 标签处理器
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        // 解析动静 SQL
        return builder.parseScriptNode();}
XMLScriptBuilder# 构造函数

初始化了动静 SQL 标签处理器

public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {super(configuration);
        this.context = context;
        this.parameterType = parameterType;
        // 初始化动静 SQL 中的节点处理器汇合
        initNodeHandlerMap();}
1.XMLScriptBuilder#initNodeHandlerMap

初始化动静 SQL 中的节点处理器汇合

private void initNodeHandlerMap() {nodeHandlerMap.put("trim", new TrimHandler());
        nodeHandlerMap.put("where", new WhereHandler());
        nodeHandlerMap.put("set", new SetHandler());
        nodeHandlerMap.put("foreach", new ForEachHandler());
        nodeHandlerMap.put("if", new IfHandler());
        nodeHandlerMap.put("choose", new ChooseHandler());
        nodeHandlerMap.put("when", new IfHandler());
        nodeHandlerMap.put("otherwise", new OtherwiseHandler());
        nodeHandlerMap.put("bind", new BindHandler());
    }
XMLScriptBuilder#parseScriptNode

解析动静 SQL

public SqlSource parseScriptNode() {
        // 解析 select\insert\ update\delete 标签中的 SQL 语句,最终将解析到的 SqlNode 封装到 MixedSqlNode 中的 List 汇合中
        // **** 将带有 ${}号的 SQL 信息封装到 TextSqlNode
        // **** 将带有 #{}号的 SQL 信息封装到 StaticTextSqlNode
        // **** 将动静 SQL 标签中的 SQL 信息别离封装到不同的 SqlNode 中
        MixedSqlNode rootSqlNode = parseDynamicTags(context);
        SqlSource sqlSource = null;
        // 如果 SQL 中蕴含 ${}和动静 SQL 语句,则将 SqlNode 封装到 DynamicSqlSource
        if (isDynamic) {sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        } else {// 如果 SQL 中蕴含 #{},则将 SqlNode 封装到 RawSqlSource 中,并指定 parameterType
            sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        }
        return sqlSource;
    }
1 XMLScriptBuilder#parseDynamicTags

解析 select\insert\ update\delete 标签中的 SQL 语句,最终将解析到的 SqlNode 封装到 MixedSqlNode 中的 List 汇合中。

  • 将带有 &dollar;{}号的 SQL 信息封装到 TextSqlNode;
  • 将带有 #{}号的 SQL 信息封装到 StaticTextSqlNode
  • 将动静 SQL 标签中的 SQL 信息别离封装到不同的 SqlNode 中
protected MixedSqlNode parseDynamicTags(XNode node) {List<SqlNode> contents = new ArrayList<>();
        // 获取 <select>\<insert> 等 4 个标签的子节点,子节点包含元素节点和文本节点
        NodeList children = node.getNode().getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {XNode child = node.newXNode(children.item(i));
            // 解决文本节点
            if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE
                    || child.getNode().getNodeType() == Node.TEXT_NODE) {String data = child.getStringBody("");
                // 将文本内容封装到 SqlNode 中
                TextSqlNode textSqlNode = new TextSqlNode(data);
                // SQL 语句中带有 ${}的话,就示意是 dynamic 的
                if (textSqlNode.isDynamic()) {contents.add(textSqlNode);
                    isDynamic = true;
                } else {// SQL 语句中(除了 ${}和上面的动静 SQL 标签),就示意是 static 的
                    // StaticTextSqlNode 的 apply 只是进行字符串的追加操作
                    contents.add(new StaticTextSqlNode(data));
                }
                
                // 解决元素节点
            } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
                String nodeName = child.getNode().getNodeName();
                // 动静 SQL 标签处理器
                // 思考,此处应用了哪种设计模式?--- 策略模式
                NodeHandler handler = nodeHandlerMap.get(nodeName);
                if (handler == null) {throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
                }
                handler.handleNode(child, contents);
                // 动静 SQL 标签是 dynamic 的
                isDynamic = true;
            }
        }
        return new MixedSqlNode(contents);
    }
2. DynamicSqlSource# 构造函数

如果 SQL 中蕴含 &dollar;{}和动静 SQL 语句,则将 SqlNode 封装到 DynamicSqlSource

public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
}
3. RawSqlSource# 构造函数

如果 SQL 中蕴含 #{},则将 SqlNode 封装到 RawSqlSource 中,并指定 parameterType

private final SqlSource sqlSource;

    // 先调用 getSql(configuration, rootSqlNode)获取 sql, 再走上面的构造函数
    public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {this(configuration, getSql(configuration, rootSqlNode), parameterType);
    }

    public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
        // 解析 SQL 语句
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        // 获取入参类型
        Class<?> clazz = parameterType == null ? Object.class : parameterType;
        // 开始解析
        sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
    }

    private static String getSql(Configuration configuration, SqlNode rootSqlNode) {DynamicContext context = new DynamicContext(configuration, null);
        rootSqlNode.apply(context);
        return context.getSql();}
3.1 SqlSourceBuilder#parse

解析 SQL 语句

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType,
                additionalParameters);
        // 创立分词解析器
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        // 解析 #{}
        String sql = parser.parse(originalSql);
        // 将解析之后的 SQL 信息,封装到 StaticSqlSource 对象中
        // SQL 字符串是带有? 号的字符串,? 相干的参数信息,封装到 ParameterMapping 汇合中
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }
3.1.1 ParameterMappingTokenHandler# 构造函数
public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType,Map<String, Object> additionalParameters) {super(configuration);
    this.parameterType = parameterType;
    this.metaParameters = configuration.newMetaObject(additionalParameters);
}
3.1.2 GenericTokenParser# 构造函数

创立分词解析器,指定待剖析的 openToken 和 closeToken,并指定处理器

 public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }
3.1.3 GenericTokenParser#parse

解析 SQL 语句,解决 openToken 和 closeToken 中的内容

/**
   * 解析 ${}和 #{}
   * @param text
   * @return
   */
  public String parse(String text) {if (text == null || text.isEmpty()) {return "";}
    // search open token
    int start = text.indexOf(openToken, 0);
    if (start == -1) {return text;}
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    while (start > -1) {if (start > 0 && src[start - 1] == '\\') {
        // this open token is escaped. remove the backslash and continue.
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();} else {
        // found open token. let's search close token.
        if (expression == null) {expression = new StringBuilder();
        } else {expression.setLength(0);
        }
        builder.append(src, offset, start - offset);
        offset = start + openToken.length();
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {if (end > offset && src[end - 1] == '\\') {
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {expression.append(src, offset, end - offset);
            offset = end + closeToken.length();
            break;
          }
        }
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();}
      }
      start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {builder.append(src, offset, src.length - offset);
    }
    return builder.toString();}
#### 3.1.3.1 ParameterMappingTokenHandler#handleToken

解决 token(#{}/&dollar;{})

@Override
public String handleToken(String content) {parameterMappings.add(buildParameterMapping(content));
    return "?";
}
#### 3.1.3.1.1 ParameterMappingTokenHandler#buildParameterMapping

创立 ParameterMapping 对象

private ParameterMapping buildParameterMapping(String content) {Map<String, String> propertiesMap = parseParameterMapping(content);
    String property = propertiesMap.get("property");
    Class<?> propertyType;
    if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
        propertyType = metaParameters.getGetterType(property);
    } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {propertyType = parameterType;} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {propertyType = java.sql.ResultSet.class;} else if (property == null || Map.class.isAssignableFrom(parameterType)) {propertyType = Object.class;} else {MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
        if (metaClass.hasGetter(property)) {propertyType = metaClass.getGetterType(property);
        } else {propertyType = Object.class;}
    }
    ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
    Class<?> javaType = propertyType;
    String typeHandlerAlias = null;
    for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {String name = entry.getKey();
        String value = entry.getValue();
        if ("javaType".equals(name)) {javaType = resolveClass(value);
            builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {builder.jdbcType(resolveJdbcType(value));
        } else if ("mode".equals(name)) {builder.mode(resolveParameterMode(value));
        } else if ("numericScale".equals(name)) {builder.numericScale(Integer.valueOf(value));
        } else if ("resultMap".equals(name)) {builder.resultMapId(value);
        } else if ("typeHandler".equals(name)) {typeHandlerAlias = value;} else if ("jdbcTypeName".equals(name)) {builder.jdbcTypeName(value);
        } else if ("property".equals(name)) {// Do Nothing} else if ("expression".equals(name)) {throw new BuilderException("Expression based parameters are not supported yet");
        } else {
            throw new BuilderException("An invalid property'" + name + "'was found in mapping #{" + content
                    + "}.  Valid properties are" + parameterProperties);
        }
    }
    if (typeHandlerAlias != null) {builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
    }
    return builder.build();}
3.1.4 StaticSqlSource# 构造函数

将解析之后的 SQL 信息,封装到 StaticSqlSource

  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Configuration configuration;

  public StaticSqlSource(Configuration configuration, String sql) {this(configuration, sql, null);
  }

  public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.configuration = configuration;
  }

7. 源码分析 - 揭秘 SqlSession 执行主流程

7.1 相干类与接口

  • DefaultSqlSession:SqlSession 接口的默认实现类
  • Executor 接口
  • BaseExecutor:根底执行器,封装了子类的公共办法,包含一级缓存、提早加载、回滚、敞开等性能;
  • SimpleExecutor:简略执行器,每执行一条 sql,都会关上一个 Statement,执行实现后敞开;
  • ReuseExecutor:重用执行器,相较于 SimpleExecutor 多了 Statement 的缓存性能,其外部保护一个 Map<String, Statement>,每次编译实现的 Statement 都会进行缓存,不会敞开;
  • BatchExecutor:批量执行器,基于 JDBC 的 addBatch、executeBatch 性能,并且在以后 sql 和上一条 sql 齐全一样的时候,重用 Statement,在调用 doFlushStatements 的时候,将数据刷新到数据库;
  • CachingExecutor:缓存执行器,装璜器模式,在开启缓存的时候。会在下面三种执行器的外面包上 CachingExecutor;
  • StatementHandler 接口:
  • RoutingStatementHandler:路由。Mybatis 理论应用的类,拦挡的 StatementHandler 理论就是它。它会依据 Exector 类型创立对应的 StatementHandler,保留到属性 delegate 中
  • PreparedStatementHandler:预编译 Statement
  • ResultSetHandler 接口:解决 Statement 执行后产生的后果集,生成后果列表;解决存储过程执行后的输入参数
  • DefaultResultSetHandler:ResultSetHandler 的 默认实现类

7.2 流程剖析

入口:DefaultSqlSession#selectList

   @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            // 依据传入的 statementId,获取 MappedStatement 对象
            MappedStatement ms = configuration.getMappedStatement(statement);
            // 调用执行器的查询方法
            // RowBounds 是用来逻辑分页(依照条件将数据从数据库查问到内存中,在内存中进行分页)// wrapCollection(parameter)是用来装璜汇合或者数组参数
            return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause:" + e, e);
        } finally {ErrorContext.instance().reset();}
    }
1. CachingExecutor#query

Configuration 中 cacheEnabled 属性值默认为 true

  // 第一步
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取绑定的 SQL 语句,比方“SELECT * FROM user WHERE id = ?”BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 生成缓存 Key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  // 第二步
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 获取二级缓存
    Cache cache = ms.getCache();
    if (cache != null) {
      // 当为 select 语句时,flushCache 默认为 false,示意任何时候语句被调用,都不会去清空本地缓存和二级缓存
      // 当为 insert、update、delete 语句时,useCache 默认为 true,示意会将本条语句的后果进行二级缓存
      // 刷新二级缓存(存在缓存且 flushCache 为 true 时)flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, boundSql);
        // 从二级缓存中查问数据
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        // 如果二级缓存中没有查问到数据,则查询数据库
        if (list == null) {
          // 委托给 BaseExecutor 执行
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 委托给 BaseExecutor 执行
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
2. BaseExecutor#query

二级缓存设置开启且缓存中没有或者未开启二级缓存,则从一级缓存中查找后果集

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      // 从一级缓存中获取数据
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 如果一级缓存没有数据,则从数据库查问数据
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {queryStack--;}
    if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();}
    }
    return list;
  }
3. BaseExecutor#queryFromDatabase

如果一级缓存没有数据,则从数据库查问数据

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 执行查问
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // 移除一级缓存中原有值
      localCache.removeObject(key);
    }
    // 往一级缓存中存值
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
4. SimpleExecutor#doQuery

  • BaseStatementHandler:根底语句处理器(抽象类),它根本把语句处理器接口的外围局部都实现了,包含配置绑定、执行器绑定、映射器绑定、参数处理器构建、后果集处理器构建、语句超时设置、语句敞开等,并另外定义了新的办法 instantiateStatement 供不同子类实现以便获取不同类型的语句连贯,子类能够一般执行 SQL 语句,也能够做预编译执行,还能够执行存储过程等。
  • SimpleStatementHandler:一般语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.Statement 对象的解决,解决一般的不带动静参数运行的 SQL,即执行简略拼接的字符串语句,同时因为 Statement 的个性,SimpleStatementHandler 每次执行都须要编译 SQL(留神:咱们晓得 SQL 的执行是须要编译和解析的)。
  • PreparedStatementHandler:预编译语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.PrepareStatement 对象的解决,相比下面的一般语句处理器,它反对可变参数 SQL 执行,因为 PrepareStatement 的个性,它会进行预编译,在缓存中一旦发现有预编译的命令,会间接解析执行,所以缩小了再次编译环节,可能无效进步零碎性能,并预防 SQL 注入攻打(所以是零碎默认也是咱们举荐的语句处理器)。
  • CallableStatementHandler:存储过程处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.CallableStatement 对象的解决,很明了,它是用来调用存储过程的,减少了存储过程的函数调用以及输入 / 输出参数的解决反对。
  • RoutingStatementHandler:路由语句处理器,间接实现了 StatementHandler 接口,作用如其名称,确确实实只是起到了路由性能,并把下面介绍到的三个语句处理器实例作为本身的委托对象而已,所以执行器在构建语句处理器时,都是间接 new 了 RoutingStatementHandler 实例。

执行查问

    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
            BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            // 获取 Configuration 对象
            Configuration configuration = ms.getConfiguration();
            // 创立 RoutingStatementHandler,用来解决 Statement
            // RoutingStatementHandler 类中初始化 delegate 类(SimpleStatementHandler、PreparedStatementHandler)StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds,
                    resultHandler, boundSql);
            // 子流程 1:设置参数
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 子流程 2:执行 SQL 语句(曾经设置过参数),并且映射后果集
            return handler.query(stmt, resultHandler);
        } finally {closeStatement(stmt);
        }
    }
4.1 Configuration#newStatementHandler

创立 StatementHandler,用来执行 MappedStatement 对象

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
            Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 创立路由性能的 StatementHandler,依据 MappedStatement 中的 StatementType
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
                rowBounds, resultHandler, boundSql);
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }
4.1.1 RoutingStatementHandler# 构造函数

创立路由性能的 StatementHandler,依据 MappedStatement 中的 StatementType

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type:" + ms.getStatementType());
    }
  }
4.2 SimpleExecutor#prepareStatement

设置参数

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 获取连贯
        Connection connection = getConnection(statementLog);
        // 创立 Statement(PreparedStatement、Statement、CallableStatement)stmt = handler.prepare(connection, transaction.getTimeout());
        // SQL 参数设置
        handler.parameterize(stmt);
        return stmt;
    }
4.2.1 BaseExecutor#getConnection

获取数据库连贯

protected Connection getConnection(Log statementLog) throws SQLException {Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {return connection;}
  }
4.2.2 BaseStatementHandler#prepare

创立 Statement(PreparedStatement、Statement、CallableStatement)

 @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // 实例化 Statement,比方 PreparedStatement
      statement = instantiateStatement(connection);
      // 设置查问超时工夫
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {closeStatement(statement);
      throw e;
    } catch (Exception e) {closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause:" + e, e);
    }
  }
4.2.2.1 PreparedStatementHandler#instantiateStatement

实例化 PreparedStatement

@Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    // 获取带有占位符的 SQL 语句
    String sql = boundSql.getSql();
    // 解决带有主键返回的 SQL
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {return connection.prepareStatement(sql);
    } else {return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }
4.2.3 PreparedStatementHandler#parameterize

SQL 参数设置, 参数映射流程会具体合成

@Override
  public void parameterize(Statement statement) throws SQLException {
    // 通过 ParameterHandler 解决参数
    parameterHandler.setParameters((PreparedStatement) statement);
  }
4.3 PreparedStatementHandler#query

执行 SQL 语句(曾经设置过参数),并且映射后果集

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;
    // 执行 PreparedStatement,也就是执行 SQL 语句
    ps.execute();
    // 处理结果集
    return resultSetHandler.handleResultSets(ps);
  }
4.3.1 PreparedStatement#execute

调用 JDBC 的 api 执行 Statement

4.3.2 DefaultResultSetHandler#handleResultSets

处理结果集 , 后果映射流程会具体合成

@Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

        // <select> 标签的 resultMap 属性,能够指定多个值,多个值之间用逗号(,)宰割
        final List<Object> multipleResults = new ArrayList<>();

        int resultSetCount = 0;
        // 这里是获取第一个后果集,将传统 JDBC 的 ResultSet 包装成一个蕴含后果列元信息的 ResultSetWrapper 对象
        ResultSetWrapper rsw = getFirstResultSet(stmt);

        // 这里是获取所有要映射的 ResultMap(依照逗号宰割进去的)List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        // 要映射的 ResultMap 的数量
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount);
        // 循环解决每个 ResultMap,从第一个开始解决
        while (rsw != null && resultMapCount > resultSetCount) {
            // 失去后果映射信息
            ResultMap resultMap = resultMaps.get(resultSetCount);
            // 处理结果集
            // 从 rsw 后果集参数中获取查问后果,再依据 resultMap 映射信息,将查问后果映射到 multipleResults 中
            handleResultSet(rsw, resultMap, multipleResults, null);

            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }

        // 对应 <select> 标签的 resultSets 属性,个别不应用该属性
        String[] resultSets = mappedStatement.getResultSets();
        if (resultSets != null) {while (rsw != null && resultSetCount < resultSets.length) {ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
                if (parentMapping != null) {String nestedResultMapId = parentMapping.getNestedResultMapId();
                    ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                    handleResultSet(rsw, resultMap, null, parentMapping);
                }
                rsw = getNextResultSet(stmt);
                cleanUpAfterHandlingResultSet();
                resultSetCount++;
            }
        }

        // 如果只有一个后果汇合,则间接从多后果集中取出第一个
        return collapseSingleResultList(multipleResults);
    }

四、总结

执行 sqlsession:参数有两个(statementId 和参数对象)
  1. 依据 statementId,去 Configuration 中的 MappedStatement 汇合中查找
    对应的 MappedStatement 对象;
  2. 取出 MappedStatement 中的 SQL 信息;
  3. 取出 MappedStatement 中的 statementType,用来创立 Statement 对象;

    • 取出 MappedStatement 中的 Configuration 对象,通过 Configuration 对象,获取 DataSource 对象,通过 DataSource 对象,创立 Connection,通过 Connection 创立 Statement 对象。
    • 设置参数
    • 执行 Statement
    • 处理结果集

8. 源码分析 - 揭秘如何设置的参数?

入口:PreparedStatementHandler#parameterize 办法

设置 PreparedStatement 的参数

 @Override
  public void parameterize(Statement statement) throws SQLException {
    // 通过 ParameterHandler 解决参数
    parameterHandler.setParameters((PreparedStatement) statement);
  }
DefaultParameterHandler#setParameters

设置参数

      @Override
    public void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        // 获取要设置的参数映射信息
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);
                // 只解决入参
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    // 获取属性名称
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    // 获取每个参数的类型处理器,去设置入参和获取返回值
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    // 获取每个参数的 JdbcType
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {jdbcType = configuration.getJdbcTypeForNull();
                    }
                    try {
                        // 给 PreparedStatement 设置参数
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (TypeException e) {
                        throw new TypeException("Could not set parameters for mapping:" + parameterMapping + ". Cause:" + e, e);
                    } catch (SQLException e) {
                        throw new TypeException("Could not set parameters for mapping:" + parameterMapping + ". Cause:" + e, e);
                    }
                }
            }
        }
    }
BaseTypeHandler#setParameter

给 PreparedStatement 设置参数

@Override
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {if (parameter == null) {if (jdbcType == null) {
                throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
            }
            try {ps.setNull(i, jdbcType.TYPE_CODE);
            } catch (SQLException e) {
                throw new TypeException("Error setting null for parameter #" + i + "with JdbcType" + jdbcType + "."
                        + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property."
                        + "Cause:" + e, e);
            }
        } else {
            try {
                // 通过 PreparedStatement 的 API 去设置非空参数
                setNonNullParameter(ps, i, parameter, jdbcType);
            } catch (Exception e) {
                throw new TypeException("Error setting non null for parameter #" + i + "with JdbcType" + jdbcType
                        + "."
                        + "Try setting a different JdbcType for this parameter or a different configuration property."
                        + "Cause:" + e, e);
            }
        }
    }
xxxTypeHandler#setNonNullParameter

通过 PreparedStatement 的 API 去设置非空参数
例如:ArrayTypeHandler

@Override
  public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {ps.setArray(i, (Array) parameter);
  }

9. 源码分析 - 后果集映射流程

入口:DefaultResultSetHandler#handleResultSets

从 rsw 后果集参数中获取查问后果,再依据 resultMap 映射信息,将查问后果映射到 multipleResults 中

@Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

        // <select> 标签的 resultMap 属性,能够指定多个值,多个值之间用逗号(,)宰割
        final List<Object> multipleResults = new ArrayList<>();

        int resultSetCount = 0;
        // 这里是获取第一个后果集,将传统 JDBC 的 ResultSet 包装成一个蕴含后果列元信息的 ResultSetWrapper 对象
        ResultSetWrapper rsw = getFirstResultSet(stmt);

        // 这里是获取所有要映射的 ResultMap(依照逗号宰割进去的)List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        // 要映射的 ResultMap 的数量
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount);
        // 循环解决每个 ResultMap,从第一个开始解决
        while (rsw != null && resultMapCount > resultSetCount) {
            // 失去后果映射信息
            ResultMap resultMap = resultMaps.get(resultSetCount);
            // 处理结果集
            // 从 rsw 后果集参数中获取查问后果,再依据 resultMap 映射信息,将查问后果映射到 multipleResults 中
            handleResultSet(rsw, resultMap, multipleResults, null);

            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }

        // 对应 <select> 标签的 resultSets 属性,个别不应用该属性
        String[] resultSets = mappedStatement.getResultSets();
        if (resultSets != null) {while (rsw != null && resultSetCount < resultSets.length) {ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
                if (parentMapping != null) {String nestedResultMapId = parentMapping.getNestedResultMapId();
                    ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                    handleResultSet(rsw, resultMap, null, parentMapping);
                }
                rsw = getNextResultSet(stmt);
                cleanUpAfterHandlingResultSet();
                resultSetCount++;
            }
        }

        // 如果只有一个后果汇合,则间接从多后果集中取出第一个
        return collapseSingleResultList(multipleResults);
    }
DefaultResultSetHandler#handleRowValues

解决行数据,其实就是实现后果映射

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
            RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
        // 是否有内置嵌套的后果映射
        if (resultMap.hasNestedResultMaps()) {ensureNoRowBounds();
            checkResultHandler();
            // 嵌套后果映射
            handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        } else {
            // 简略后果映射
            handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        }
    }
DefaultResultSetHandler#handleRowValuesForSimpleResultMap

简略后果映射

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
            ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
        // 获取后果集信息
        ResultSet resultSet = rsw.getResultSet();
        // 应用 rowBounds 的分页信息,进行逻辑分页(也就是在内存中分页)skipRows(resultSet, rowBounds);
        while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
            // 通过 <resultMap> 标签的子标签 <discriminator> 对后果映射进行甄别
            ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
            // 将查问后果封装到 POJO 中
            Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
            // 解决对象嵌套的映射关系
            storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
    }
1. DefaultResultSetHandler#getRowValue

将查问后果封装到 POJO 中

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
        // 提早加载的映射信息
        final ResultLoaderMap lazyLoader = new ResultLoaderMap();
        // 创立要映射的 PO 类对象
        Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
        if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {final MetaObject metaObject = configuration.newMetaObject(rowValue);
            boolean foundValues = this.useConstructorMappings;
            // 是否利用主动映射,也就是通过 resultType 进行映射
            if (shouldApplyAutomaticMappings(resultMap, false)) {
                // 依据 columnName 和 type 属性名映射赋值
                foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
            }
            // 依据咱们配置 ResultMap 的 column 和 property 映射赋值
            // 如果映射存在 nestedQueryId,会调用 getNestedQueryMappingValue 办法获取返回值
            foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
            foundValues = lazyLoader.size() > 0 || foundValues;
            rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}
        return rowValue;
    }
1.1 DefaultResultSetHandler#createResultObject

创立映射后果对象

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
            String columnPrefix) throws SQLException {
        this.useConstructorMappings = false; // reset previous mapping result
        final List<Class<?>> constructorArgTypes = new ArrayList<>();
        final List<Object> constructorArgs = new ArrayList<>();
        // 创立后果映射的 PO 类对象
        Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
        if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            // 获取要映射的 PO 类的属性信息
            final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
            for (ResultMapping propertyMapping : propertyMappings) {
                // issue gcode #109 && issue #149
                // 提早加载解决
                if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
                    // 通过动静代理工厂,创立提早加载的代理对象
                    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,
                            objectFactory, constructorArgTypes, constructorArgs);
                    break;
                }
            }
        }
        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping
                                                                                                // result
        return resultObject;
    }

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
            List<Object> constructorArgs, String columnPrefix) throws SQLException {final Class<?> resultType = resultMap.getType();
        final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
        final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
        if (hasTypeHandlerForResultObject(rsw, resultType)) {return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
        } else if (!constructorMappings.isEmpty()) {
            return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes,
                    constructorArgs, columnPrefix);
        } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
            // 对象工厂创建对象
            return objectFactory.create(resultType);
        } else if (shouldApplyAutomaticMappings(resultMap, false)) {return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
        }
        throw new ExecutorException("Do not know how to create an instance of" + resultType);
    }
1.2 DefaultResultSetHandler#applyAutomaticMappings

依据 columnName 和 type 属性名映射赋值

private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
            String columnPrefix) throws SQLException {List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
        boolean foundValues = false;
        if (!autoMapping.isEmpty()) {for (UnMappedColumnAutoMapping mapping : autoMapping) {final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
                if (value != null) {foundValues = true;}
                if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {// gcode issue #377, call setter on nulls (value is not 'found')
                    metaObject.setValue(mapping.property, value);
                }
            }
        }
        return foundValues;
    }
1.3 DefaultResultSetHandler#applyPropertyMappings

依据咱们配置 ResultMap 的 column 和 property 映射赋值, 如果映射存在 nestedQueryId,会调用 getNestedQueryMappingValue 办法获取返回值

private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
            ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
        boolean foundValues = false;
        final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
        for (ResultMapping propertyMapping : propertyMappings) {String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
            if (propertyMapping.getNestedResultMapId() != null) {
                // the user added a column attribute to a nested result map, ignore it
                column = null;
            }
            if (propertyMapping.isCompositeResult()
                    || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
                    || propertyMapping.getResultSet() != null) {Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader,
                        columnPrefix);
                // issue #541 make property optional
                final String property = propertyMapping.getProperty();
                if (property == null) {continue;} else if (value == DEFERRED) {
                    foundValues = true;
                    continue;
                }
                if (value != null) {foundValues = true;}
                if (value != null || (configuration.isCallSettersOnNulls()
                        && !metaObject.getSetterType(property).isPrimitive())) {// gcode issue #377, call setter on nulls (value is not 'found')
                    metaObject.setValue(property, value);
                }
            }
        }
        return foundValues;
    }

10. 源码分析 - 获取 Mapper 代理对象流程

入口:DefaultSqlSession#getMapper

从 Configuration 对象中,依据 Mapper 接口,获取 Mapper 代理对象

    @Override
    public <T> T getMapper(Class<T> type) {
        // 从 Configuration 对象中,依据 Mapper 接口,获取 Mapper 代理对象
        return configuration.<T>getMapper(type, this);
    }
Configuration#getMapper

获取 Mapper 代理对象

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

通过代理对象工厂,获取代理对象:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 依据 Mapper 接口的类型,从 Map 汇合中获取 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 {
      // 通过 MapperProxyFactory 生产 MapperProxy,通过 MapperProxy 产生 Mapper 代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause:" + e, e);
    }
  }
1.1 MapperProxyFactory#newInstance

调用 JDK 的动静代理形式,创立 Mapper 代理

  //1
 protected T newInstance(MapperProxy<T> mapperProxy) {
    // 应用 JDK 动静代理形式,生成代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface}, mapperProxy);
  }

//2
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 依据 Mapper 接口的类型,从 Map 汇合中获取 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 {
      // 通过 MapperProxyFactory 生产 MapperProxy,通过 MapperProxy 产生 Mapper 代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause:" + e, e);
    }
  }

11. 源码分析 -invoke 办法

// 通过 JDK 动静代理生成并获取代理对象       
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 代理对象对象调用办法,底层执行 invoke 办法       
List<User> allUser = userMapper.findAllUser();

在动静代理返回了示例后,咱们就能够间接调用 mapper 类中的办法了,但 代理对象调用办法,执行是在 MapperProxy 中的 invoke 办法 ,该类实现InvocationHandler 接口,并重写 invoke()办法。

问题:invoke 办法执行逻辑是什么?

入口:MapperProxy#invoke

 @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 如果是 Object 定义的办法,间接调用
            if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);

            } else if (isDefaultMethod(method)) {return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);
        }
        // 取得 MapperMethod 对象
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // 重点在这:MapperMethod 最终调用了执行的办法
        return mapperMethod.execute(sqlSession, args);
    }
MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        // 判断 mapper 中的办法类型,最终调用的还是 SqlSession 中的办法
        switch (command.getType()) {
            case INSERT: {
                // 转换参数
                Object param = method.convertArgsToSqlCommandParam(args);
                // 执行 INSERT 操作
                // 转换 rowCount
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                // 转换参数
                Object param = method.convertArgsToSqlCommandParam(args);
                // 转换 rowCount
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                // 转换参数
                Object param = method.convertArgsToSqlCommandParam(args);
                // 转换 rowCount
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                // 无返回,并且有 ResultHandler 办法参数,则将查问的后果,提交给 ResultHandler 进行解决
                if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);
                    result = null;
                // 执行查问,返回列表
                } else if (method.returnsMany()) {result = executeForMany(sqlSession, args);
                // 执行查问,返回 Map
                } else if (method.returnsMap()) {result = executeForMap(sqlSession, args);
                // 执行查问,返回 Cursor
                } 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());
        }
        // 返回后果为 null,并且返回类型为根本类型,则抛出 BindingException 异样
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method'" + command.getName()
                    + "attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        // 返回后果
        return result;
    }

12. 源码分析 - 插件机制

12.1 插件概述

  • 问题:什么是 Mybatis 插件?有什么作用?

个别开源框架都会提供扩大点,让开发者自行扩大,从而实现逻辑的加强。

基于插件机制能够实现了很多有用的性能,比如说分页,字段加密,监控等性能,这种通用的性能,就如同 AOP 一样,横切在数据操作上

而通过 Mybatis 插件能够实现对框架的扩大,来实现自定义性能,并且对于用户是无感知的。

12.2 Mybatis 插件介绍

Mybatis 插件实质上来说就是一个拦截器,它体现了JDK 动静代理和责任链设计模式的综合使用

Mybatis 中针对四大组件提供了扩大机制,这四个组件别离是:

Mybatis 中所容许拦挡的办法如下:

  • Executor【SQL 执行器】【update,query,commit,rollback】
  • StatementHandler【Sql 语法构建器对象】【prepare,parameterize,batch,update,query 等】
  • ParameterHandler【参数处理器】【getParameterObject,setParameters 等】
  • ResultSetHandler【后果集处理器】【handleResultSets,handleOuputParameters 等】
能干什么?
  • 分页性能:mybatis 的分页默认是基于内存分页的(查出所有,再截取),数据量大的状况下效率较低,不过应用 mybatis 插件能够扭转该行为,只须要拦挡 StatementHandler 类的 prepare 办法,扭转要执行的 SQL 语句为分页语句即可
  • 性能监控:对于 SQL 语句执行的性能监控,能够通过拦挡 Executor 类的 update, query 等办法,用日志记录每个办法执行的工夫
如何自定义插件?

在应用之前,咱们先来看看 Mybatis 提供的插件相干的类,过一遍它们别离提供了哪些性能,最初咱们本人定义一个插件

用于定义插件的类

后面曾经晓得 Mybatis 插件是能够对 Mybatis 中四大组件对象的办法进行拦挡,那拦截器拦挡哪个类的哪个办法如何晓得,就由上面这个注解提供拦挡信息

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {Signature[] value();}

因为一个拦截器能够同时拦挡多个对象的多个办法,所以就应用了 Signture 数组,该注解定义了拦挡的残缺信息

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
  // 拦挡的类
  Class<?> type();
  // 拦挡的办法
  String method();
  // 拦挡办法的参数    
  Class<?>[] args();
  
 } 

曾经晓得了该拦挡哪些对象的哪些办法,拦挡后要干什么就须要实现 Intercetor#intercept 办法,在这个办法外面实现拦挡后的解决逻辑


public interface Interceptor {
  /**
   * 真正办法被拦挡执行的逻辑
   *
   * @param invocation 次要目标是将多个参数进行封装
   */
  Object intercept(Invocation invocation) throws Throwable;
    
  // 生成指标对象的代理对象
  default Object plugin(Object target) {return Plugin.wrap(target, this);
  }
  // 能够拦截器设置一些属性
  default void setProperties(Properties properties) {// NOP}
}

12.3 自定义插件

需要:把 Mybatis 所有执行的 sql 都记录下来

步骤:① 创立 Interceptor 的实现类,重写办法

​ ② 应用 @Intercepts 注解实现插件签名 阐明插件的拦挡四大对象之一的哪一个对象的哪一个办法

​ ③ 将写好的插件注册到全局配置文件中

①. 创立 Interceptor 的实现类

public class MyPlugin implements Interceptor {private final Logger logger = LoggerFactory.getLogger(this.getClass());
  // // 这里是每次执行操作的时候,都会进行这个拦截器的办法内 
  
  Override
  public Object intercept(Invocation invocation) throws Throwable { 
  // 加强逻辑
   StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        logger.info("mybatis intercept sql:{}", sql);
    return invocation.proceed(); // 执行原办法} 
  
  /**
  *
  *  ^Description 包装指标对象 为指标对象创立代理对象
  *  @Param target 为要拦挡的对象
  *  @Return 代理对象
  */
  Override 
  public Object plugin(Object target) {System.out.println("将要包装的指标对象:"+target); 
    return Plugin.wrap(target,this);
    }
    
  /** 获取配置文件的属性 **/
  // 插件初始化的时候调用,也只调用一次,插件配置的属性从这里设置进来
  Override
    public void setProperties(Properties properties) {System.out.println("插件配置的初始化参数:"+properties);
  }
}

② 应用 @Intercepts 注解实现插件签名 阐明插件的拦挡四大对象之一的哪一个对象的哪一个办法

@Intercepts({ @Signature(type = StatementHandler.class, 
                         method = "prepare", 
                         args = {Connection.class, Integer.class}) })
public class SQLStatsInterceptor implements Interceptor {

③ 将写好的插件注册到全局配置文件中

<?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>
    <plugins>
        <plugin interceptor="com.itheima.interceptor.MyPlugin">
            <property name="dialect" value="mysql" />
        </plugin>
    </plugins>
</configuration>
核心思想:

就是应用 JDK 动静代理的形式,对这四个对象进行包装加强。具体的做法是,创立一个类实现 Mybatis 的拦截器接口,并且退出到拦截器链中,在创立外围对象的时候,不间接返回,而是遍历拦截器链,把每一个拦截器都作用于外围对象中。这么一来,Mybatis 创立的外围对象其实都是代理对象,都是被包装过的。

12.4 源码剖析 - 插件

  • 插件的初始化:插件对象是如何实例化的?插件的实例对象如何增加到拦截器链中的?组件对象的代理对象是如何产生的?
  • 拦挡逻辑的执行
插件配置信息的加载

咱们定义好了一个拦截器,那咱们怎么通知 Mybatis 呢?Mybatis 所有的配置都定义在 XXx.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>
    <plugins>
        <plugin interceptor="com.itheima.interceptor.MyPlugin">
            <property name="dialect" value="mysql" />
        </plugin>
    </plugins>
</configuration>

对应的解析代码如下(XMLConfigBuilder#pluginElement):

private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {
        // 获取拦截器
        String interceptor = child.getStringAttribute("interceptor");
        // 获取配置的 Properties 属性
        Properties properties = child.getChildrenAsProperties();
        // 依据配置文件中配置的插件类的全限定名 进行反射初始化
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        // 将属性增加到 Intercepetor 对象
        interceptorInstance.setProperties(properties);
        // 增加到配置类的 InterceptorChain 属性,InterceptorChain 类保护了一个 List<Interceptor>
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

次要做了以下工作:

  1. 遍历解析 plugins 标签下每个 plugin 标签
  2. 依据解析的类信息创立 Interceptor 对象
  3. 调用 setProperties 办法设置属性
  4. 将拦截器增加到 Configuration 类的 IntercrptorChain 拦截器链中

对应时序图如下:

代理对象的生成

Executor 代理对象(Configuration#newExecutor)


public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);
    } else {executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {executor = new CachingExecutor(executor);
    }
    // 生成 Executor 代理对象逻辑
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

ParameterHandler 代理对象(Configuration#newParameterHandler)

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 生成 ParameterHandler 代理对象逻辑 
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

ResultSetHandler 代理对象(Configuration#newResultSetHandler)


public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    // 生成 ResultSetHandler 代理对象逻辑
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

StatementHandler 代理对象(Configuration#newStatementHandler)

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 生成 StatementHandler 代理对象逻辑
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;

}

通过查看源码会发现,所有代理对象的生成都是通过 InterceptorChain#pluginAll 办法来创立的,进一步查看 pluginAll 办法


public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);
    }
    return target;

}

InterceptorChain#pluginAll 外部通过遍历 Interceptor#plugin 办法来创立代理对象,并将生成的代理对象又赋值给 target, 如果存在多个拦截器的话,生成的代理对象会被另一个代理对象所代理,从而造成一个代理链,执行的时候,顺次执行所有拦截器的拦挡逻辑代码,咱们再跟进去


default Object plugin(Object target) {return Plugin.wrap(target, this);
}

Interceptor#plugin办法最终将指标对象和以后的拦截器交给 Plugin.wrap 办法来创立代理对象。该办法是默认办法,是 Mybatis 框架提供的一个典型 plugin 办法的实现。让咱们看看在 Plugin#wrap 办法中是如何实现代理对象的

public static Object wrap(Object target, Interceptor interceptor) {
    // 1. 解析该拦截器所拦挡的所有接口及对应拦挡接口的办法
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    // 2. 获取指标对象实现的所有被拦挡的接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    // 3. 指标对象有实现被拦挡的接口,生成代理对象并返回
    if (interfaces.length > 0) {
        // 通过 JDK 动静代理的形式实现
      return Proxy.newProxyInstance(type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    // 指标对象没有实现被拦挡的接口,间接返回原对象
    return target;

}

最终咱们看到其实是通过 JDK 提供的 Proxy.newProxyInstance 办法来生成代理对象

以上代理对象生成过程的时序图如下:

拦挡逻辑的执行

通过下面的剖析,咱们晓得 Mybatis 框架中执行 Executor、ParameterHandler、ResultSetHandler 和 StatementHandler 中的办法时真正执行的是代理对象对应的办法。而且该代理对象是通过 JDK 动静代理生成的,所以执行办法时实际上是调用 InvocationHandler#invoke 办法(Plugin 类实现 InvocationHandler 接口), 上面是 Plugin#invoke 办法

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);
    }
}

注:一个对象被代理很屡次

问题:同一个组件对象的同一个办法是否能够被多个拦截器进行拦挡?

答案是必定的,所以咱们 配置在最后面的拦截器最先被代理,然而执行的时候却是最外层的先执行

具体点:

如果顺次定义了三个插件:插件 1 插件 2 和 插件 3

那么 List 中就会按顺序存储:插件 1 插件 2 插件 3

而解析的时候是遍历 list,所以解析的时候也是依照:插件 1 ,插件 2 ,插件 3 的程序。

然而执行的时候就要反过来了,执行的时候是依照:插件 3 插件 2 插件 1 的程序进行执行。

当 Executor 的某个办法被调用的时候,插件逻辑会后行执行。执行程序由外而内,比方上图的执行程序为 plugin3 → plugin2 → Plugin1 → Executor

13. 源码分析 - 缓存策略

缓存就是内存中的数据,经常来自对数据库查问后果的保留。应用缓存,咱们能够防止频繁的与数据库进行交互,进而进步响应速度 MyBatis 也提供了对缓存的反对,分为一级缓存和二级缓存,能够通过下图来了解:

①、一级缓存是 SqlSession 级别的缓存。在操作数据库时须要结构 sqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的 sqlSession 之间的缓存数据区域(HashMap)是相互不影响的。

②、二级缓存是 mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 能够共用二级缓存,二级缓存是跨 SqlSession 的

一级缓存

默认是开启的

①、咱们应用同一个 sqlSession,对 User 表依据雷同 id 进行两次查问,查看他们收回 sql 语句的状况

  @Test
  public void firstLevelCacheTest() throws IOException {

    // 1. 通过类加载器对配置文件进行加载,加载成了字节输出流,存到内存中 留神:配置文件并没有被解析
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. (1)解析了配置文件,封装 configuration 对象(2)创立了 DefaultSqlSessionFactory 工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3. 问题:openSession()执行逻辑是什么?// 3. (1)创立事务对象(2)创立了执行器对象 cachingExecutor (3)创立了 DefaultSqlSession 对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4. 委派给 Executor 来执行,Executor 执行时又会调用很多其余组件(参数设置、解析 sql 的获取,sql 的执行、后果集的封装)User user = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);
    User user2 = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    System.out.println(user == user2);

    sqlSession.close();}

查看控制台打印状况:

② 同样是对 user 表进行两次查问,只不过两次查问之间进行了一次 update 操作。

  @Test
  public void test3() throws IOException {

    // 1. 通过类加载器对配置文件进行加载,加载成了字节输出流,存到内存中 留神:配置文件并没有被解析
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. (1)解析了配置文件,封装 configuration 对象(2)创立了 DefaultSqlSessionFactory 工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3. 问题:openSession()执行逻辑是什么?// 3. (1)创立事务对象(2)创立了执行器对象 cachingExecutor (3)创立了 DefaultSqlSession 对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4. 委派给 Executor 来执行,Executor 执行时又会调用很多其余组件(参数设置、解析 sql 的获取,sql 的执行、后果集的封装)User user = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    User user1 = new User();
    user1.setId(1);
    user1.setUsername("zimu");
    sqlSession.update("com.itheima.mapper.UserMapper.updateUser",user1);
    sqlSession.commit();
    User user2 = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    System.out.println(user == user2);
    System.out.println(user);
    System.out.println(user2);
    System.out.println("MyBatis 源码环境搭建胜利....");

    sqlSession.close();}

查看控制台打印状况:

③、总结

1、第一次发动查问用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从 数据库查问用户信息。失去用户信息,将用户信息存储到一级缓存中。

2、如果两头 sqlSession 去执行 commit 操作(执行插入、更新、删除),则会清空 SqlSession 中的 一级缓存,这样做的目标为了让缓存中存储的是最新的信息,防止脏读。

3、第二次发动查问用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直 接从缓存中获取用户信息

一级缓存原理探索与源码剖析

问题 1:一级缓存 底层数据结构到底是什么?

问题 2:一级缓存的工作流程是怎么的?

1. 一级缓存 底层数据结构到底是什么?

之前说 不同 SqlSession 的一级缓存互不影响, 所以我从 SqlSession 这个类动手

能够看到,org.apache.ibatis.session.SqlSession中有一个和缓存无关的办法——clearCache()刷新缓存的办法,点进去,找到它的实现类DefaultSqlSession

  @Override
  public void clearCache() {executor.clearLocalCache();
  }

再次点进去executor.clearLocalCache(), 再次点进去并找到其实现类BaseExecutor

  @Override
  public void clearLocalCache() {if (!closed) {localCache.clear();
      localOutputParameterCache.clear();}
  

进入 localCache.clear() 办法。进入到了 org.apache.ibatis.cache.impl.PerpetualCache 类中

package org.apache.ibatis.cache.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
/**
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {
  private final String id;

  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {this.id = id;}

  // 省略局部...
  @Override
  public void clear() {cache.clear();
  }
  // 省略局部...
}

咱们看到了 PerpetualCache 类中有一个属性 private Map<Object, Object> cache = new HashMap<Object, Object>(),很显著它是一个 HashMap,咱们所调用的.clear() 办法,实际上就是调用的 Map 的 clear 办法

得出结论:

一级缓存的数据结构的确是 HashMap

2. 一级缓存的执行流程

咱们进入到 org.apache.ibatis.executor.Executor
看到一个办法 CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql),见名思意是一个创立 CacheKey 的办法
找到它的实现类和办法org.apache.ibatis.executor.BaseExecuto.createCacheKey

咱们剖析一下创立 CacheKey 的这块代码:

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {if (closed) {throw new ExecutorException("Executor was closed.");
    }
    // 初始化 CacheKey
    CacheKey cacheKey = new CacheKey();
    // 存入 statementId
    cacheKey.update(ms.getId());
    // 别离存入分页须要的 Offset 和 Limit
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    // 把从 BoundSql 中封装的 sql 取出并存入到 cacheKey 对象中
    cacheKey.update(boundSql.getSql());
    // 上面这一块就是封装参数
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();

    for (ParameterMapping parameterMapping : parameterMappings) {if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    // 从 configuration 对象中(也就是载入配置文件后寄存的对象)把 EnvironmentId 存入
        /**
     *     <environments default="development">
     *         <environment id="development"> // 就是这个 id
     *             <!-- 以后事务交由 JDBC 进行治理 -->
     *             <transactionManager type="JDBC"></transactionManager>
     *             <!-- 以后应用 mybatis 提供的连接池 -->
     *             <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>
     */
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    // 返回
    return cacheKey;
  }

咱们再点进去 cacheKey.update() 办法看一看


public class CacheKey implements Cloneable, Serializable {
  private static final long serialVersionUID = 1146682552656046210L;
  public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
  private static final int DEFAULT_MULTIPLYER = 37;
  private static final int DEFAULT_HASHCODE = 17;

  private final int multiplier;
  private int hashcode;
  private long checksum;
  private int count;
  // 值存入的中央
  private transient List<Object> updateList;
  // 省略局部办法......
  // 省略局部办法......
  public void update(Object object) {int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); 
    count++;
    checksum += baseHashCode;
    baseHashCode *= count;
    hashcode = multiplier * hashcode + baseHashCode;
    // 看到把值传入到了一个 list 中
    updateList.add(object);
  }
 
  // 省略局部办法......
}

咱们晓得了那些数据是在 CacheKey 对象中如何存储的了。上面咱们返回 createCacheKey() 办法。

咱们进入 BaseExecutor,能够看到一个query() 办法:

这里咱们很分明的看到,在执行 query() 办法前,CacheKey办法被创立了

咱们能够看到,创立 CacheKey 后调用了 query()办法,咱们再次点进去:

在执行 SQL 前如何在一级缓存中找不到 Key,那么将会执行 sql,咱们来看一下执行 sql 前后会做些什么,进入list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

剖析一下:

 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //1. 把 key 存入缓存,value 放一个占位符
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //2. 与数据库交互
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      //3. 如果第 2 步出了什么异样,把第 1 步存入的 key 删除
      localCache.removeObject(key);
    }
      //4. 把后果存入缓存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
一级缓存源码剖析论断:
  1. 一级缓存的数据结构是一个 HashMap<Object,Object>,它的 value 就是查问后果,它的 key 是CacheKeyCacheKey 中有一个 list 属性,statementId,params,rowbounds,sql等参数都存入到了这个 list
  2. 先创立 CacheKey,会首先依据CacheKey 查问缓存中有没有,如果有,就解决缓存中的参数,如果没有,就执行 sql,执行 sql 后执行 sql 后把后果存入缓存

二级缓存

留神:Mybatis 的二级缓存不是默认开启的,是须要通过配置能力应用的

启用二级缓存

分为三步走:

1)开启映射器配置文件中的缓存配置:

 <settings>
    <setting name="cacheEnabled" value="true"/>
 </settings>
  1. 在须要应用二级缓存的 Mapper 配置文件中配置标签
  <!--type:cache 应用的类型,默认是 PerpetualCache,这在一级缓存中提到过。eviction:定义回收的策略,常见的有 FIFO,LRU。flushInterval:配置肯定工夫主动刷新缓存,单位是毫秒。size:最多缓存对象的个数。readOnly:是否只读,若配置可读写,则须要对应的实体类可能序列化。blocking:若缓存中找不到对应的 key,是否会始终 blocking,直到有对应的数据进入缓存。-->  
<cache></cache>

3)在具体 CURD 标签上配置 useCache=true

   <select id="findById" resultType="com.itheima.pojo.User" useCache="true">
       select * from user where id = #{id}
   </select>

** 留神:实体类要实现 Serializable 接口,因为二级缓存会将对象写进硬盘,就必须序列化,以及兼容对象在网络中的传输

具体实现

  /**
   * 测试一级缓存
   */
  @Test
  public void secondLevelCacheTest() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. (1)解析了配置文件,封装 configuration 对象(2)创立了 DefaultSqlSessionFactory 工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3. 问题:openSession()执行逻辑是什么?// 3. (1)创立事务对象(2)创立了执行器对象 cachingExecutor (3)创立了 DefaultSqlSession 对象
    SqlSession sqlSession1 = sqlSessionFactory.openSession();

    // 发动第一次查问,查问 ID 为 1 的用户
    User user1 = sqlSession1.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    // *** 必须调用 sqlSession1.commit()或者 close(), 一级缓存中的内容才会刷新到二级缓存中
    sqlSession1.commit();// close();
    // 发动第二次查问,查问 ID 为 1 的用户
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    User user2 = sqlSession2.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    System.out.println(user1 == user2);
    System.out.println(user1);
    System.out.println(user2);

    sqlSession1.close();
    sqlSession2.close();}

二级缓存源码剖析

问题:

① cache 标签如何被解析的(二级缓存的底层数据结构是什么?)?

② 同时开启一级缓存二级缓存,优先级?

③ 为什么只有执行 sqlSession.commit 或者 sqlSession.close 二级缓存才会失效

④ 更新办法为什么不会清空二级缓存?

标签 < cache/> 的解析

二级缓存和具体的命名空间绑定,一个 Mapper 中有一个 Cache, 雷同 Mapper 中的 MappedStatement 共用同一个 Cache

依据之前的 mybatis 源码分析,xml 的解析工作次要交给 XMLConfigBuilder.parse()办法来实现

  // XMLConfigBuilder.parse()
  public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");
      }
      parsed = true;
      parseConfiguration(parser.evalNode("/configuration"));// 在这里
      return configuration;
  }
  
 // parseConfiguration()
 // 既然是在 xml 中增加的,那么咱们就间接看对于 mappers 标签的解析
 private void parseConfiguration(XNode root) {
     try {Properties settings = settingsAsPropertiess(root.evalNode("settings"));
         propertiesElement(root.evalNode("properties"));
         loadCustomVfs(settings);
         typeAliasesElement(root.evalNode("typeAliases"));
         pluginElement(root.evalNode("plugins"));
         objectFactoryElement(root.evalNode("objectFactory"));
         objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
         reflectionFactoryElement(root.evalNode("reflectionFactory"));
         settingsElement(settings);
         // read it after objectFactory and objectWrapperFactory issue #631
         environmentsElement(root.evalNode("environments"));
         databaseIdProviderElement(root.evalNode("databaseIdProvider"));
         typeHandlerElement(root.evalNode("typeHandlers"));
         // 就是这里
         mapperElement(root.evalNode("mappers"));
     } catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause:" + e, e);
     }
 }
 
 
 // mapperElement()
 private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");
                 configuration.addMappers(mapperPackage);
             } else {String resource = child.getStringAttribute("resource");
                 String url = child.getStringAttribute("url");
                 String mapperClass = child.getStringAttribute("class");
                 // 依照咱们本例的配置,则间接走该 if 判断
                 if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);
                     InputStream inputStream = Resources.getResourceAsStream(resource);
                     XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                     // 生成 XMLMapperBuilder,并执行其 parse 办法
                     mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);
                     InputStream inputStream = Resources.getUrlAsStream(url);
                     XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                     mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);
                     configuration.addMapper(mapperInterface);
                 } else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                 }
             }
         }
     }
 } 

咱们来看看解析 Mapper.xml

// XMLMapperBuilder.parse()
public void parse() {if (!configuration.isResourceLoaded(resource)) {
        // 解析 mapper 属性
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();}
 
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();}
 
// configurationElement()
private void configurationElement(XNode context) {
    try {String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        cacheRefElement(context.evalNode("cache-ref"));
        // 最终在这里看到了对于 cache 属性的解决
        cacheElement(context.evalNode("cache"));
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        sqlElement(context.evalNodes("/mapper/sql"));
        // 这里会将生成的 Cache 包装到对应的 MappedStatement
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. Cause:" + e, e);
    }
}
 
// cacheElement()
private void cacheElement(XNode context) throws Exception {if (context != null) {
        // 解析 <cache/> 标签的 type 属性,这里咱们能够自定义 cache 的实现类,比方 redisCache,如果没有自定义,这里应用和一级缓存雷同的 PERPETUAL
        String type = context.getStringAttribute("type", "PERPETUAL");
        Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        String eviction = context.getStringAttribute("eviction", "LRU");
        Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
        Long flushInterval = context.getLongAttribute("flushInterval");
        Integer size = context.getIntAttribute("size");
        boolean readWrite = !context.getBooleanAttribute("readOnly", false);
        boolean blocking = context.getBooleanAttribute("blocking", false);
        Properties props = context.getChildrenAsProperties();
        // 构建 Cache 对象
        builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
}

先来看看是如何构建 Cache 对象的

MapperBuilderAssistant.useNewCache()

public Cache useNewCache(Class<? extends Cache> typeClass,
                         Class<? extends Cache> evictionClass,
                         Long flushInterval,
                         Integer size,
                         boolean readWrite,
                         boolean blocking,
                         Properties props) {
    // 1. 生成 Cache 对象
    Cache cache = new CacheBuilder(currentNamespace)
         // 这里如果咱们定义了 <cache/> 中的 type,就应用自定义的 Cache, 否则应用和一级缓存雷同的 PerpetualCache
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    // 2. 增加到 Configuration 中
    configuration.addCache(cache);
    // 3. 并将 cache 赋值给 MapperBuilderAssistant.currentCache
    currentCache = cache;
    return cache;
}

咱们看到一个 Mapper.xml 只会解析一次

标签,也就是只创立一次 Cache 对象,放进 configuration 中,并将 cache 赋值给 MapperBuilderAssistant.currentCache

buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)); 将 Cache 包装到 MappedStatement
// buildStatementFromContext()
private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
}
 
//buildStatementFromContext()
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
            // 每一条执行语句转换成一个 MappedStatement
            statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);
        }
    }
}
 
// XMLStatementBuilder.parseStatementNode();
public void parseStatementNode() {String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    ...
 
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);
 
    ...
    // 创立 MappedStatement 对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                                        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                                        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
                                        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
 
// builderAssistant.addMappedStatement()
public MappedStatement addMappedStatement(
    String id,
    ...) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");
    }
 
    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 创立 MappedStatement 对象
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        ...
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);// 在这里将之前生成的 Cache 封装到 MappedStatement
 
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);
    }
 
    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
}

咱们看到将 Mapper 中创立的 Cache 对象,退出到了每个 MappedStatement 对象中,也就是同一个 Mapper 中所有的 MappedStatement 中的 cache 属性援用的是同一个

有对于

标签的解析就到这了。

查问源码剖析
CachingExecutor
// CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创立 CacheKey
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
    // 从 MappedStatement 中获取 Cache,留神这里的 Cache 是从 MappedStatement 中获取的
    // 也就是咱们下面解析 Mapper 中 <cache/> 标签中创立的,它保留在 Configration 中
    // 咱们在下面解析 blog.xml 时剖析过每一个 MappedStatement 都有一个 Cache 对象,就是这里
    Cache cache = ms.getCache();
    // 如果配置文件中没有配置 <cache>,则 cache 为空
    if (cache != null) {
        // 如果须要刷新缓存的话就刷新:flushCache="true"
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, boundSql);
            // 拜访二级缓存
            List<E> list = (List<E>) tcm.getObject(cache, key);
            // 缓存未命中
            if (list == null) {
                // 如果没有值,则执行查问,这个查问理论也是先走一级缓存查问,一级缓存也没有的话,则进行 DB 查问
                list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                // 缓存查问后果
                tcm.putObject(cache, key, list);
            }
            return list;
        }
    }
    return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

如果设置了flushCache=”true”,则每次查问都会刷新缓存

<!-- 执行此语句清空缓存 -->
<select id="findbyId" resultType="com.itheima.pojo.user" useCache="true" flushCache="true" >
    select * from t_demo
</select>

如上,留神二级缓存是从 MappedStatement 中获取的。因为 MappedStatement 存在于全局配置中,能够多个 CachingExecutor 获取到,这样就会呈现线程平安问题。除此之外,若不加以控制,多个事务共用一个缓存实例,会导致脏读问题。至于脏读问题,须要借助其余类来解决,也就是下面代码中 tcm 变量对应的类型。上面剖析一下。

TransactionalCacheManager
/** 事务缓存管理器 */
public class TransactionalCacheManager {

    // Cache 与 TransactionalCache 的映射关系表
    private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();

    public void clear(Cache cache) {
        // 获取 TransactionalCache 对象,并调用该对象的 clear 办法,下同
        getTransactionalCache(cache).clear();}

    public Object getObject(Cache cache, CacheKey key) {
        // 间接从 TransactionalCache 中获取缓存
        return getTransactionalCache(cache).getObject(key);
    }

    public void putObject(Cache cache, CacheKey key, Object value) {
        // 间接存入 TransactionalCache 的缓存中
        getTransactionalCache(cache).putObject(key, value);
    }

    public void commit() {for (TransactionalCache txCache : transactionalCaches.values()) {txCache.commit();
        }
    }

    public void rollback() {for (TransactionalCache txCache : transactionalCaches.values()) {txCache.rollback();
        }
    }

    private TransactionalCache getTransactionalCache(Cache cache) {
        // 从映射表中获取 TransactionalCache
        TransactionalCache txCache = transactionalCaches.get(cache);
        if (txCache == null) {
            // TransactionalCache 也是一种装璜类,为 Cache 减少事务性能
            // 创立一个新的 TransactionalCache,并将真正的 Cache 对象存进去
            txCache = new TransactionalCache(cache);
            transactionalCaches.put(cache, txCache);
        }
        return txCache;
    }
}

TransactionalCacheManager 外部保护了 Cache 实例与 TransactionalCache 实例间的映射关系,该类也仅负责保护两者的映射关系,真正做事的还是 TransactionalCache。TransactionalCache 是一种缓存装璜器,能够为 Cache 实例减少事务性能。上面剖析一下该类的逻辑。

TransactionalCache
public class TransactionalCache implements Cache {
    // 真正的缓存对象,和下面的 Map<Cache, TransactionalCache> 中的 Cache 是同一个
    private final Cache delegate;
    private boolean clearOnCommit;
    // 在事务被提交前,所有从数据库中查问的后果将缓存在此汇合中
    private final Map<Object, Object> entriesToAddOnCommit;
    // 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此汇合中
    private final Set<Object> entriesMissedInCache;


    @Override
    public Object getObject(Object key) {
        // 查问的时候是间接从 delegate 中去查问的,也就是从真正的缓存对象中查问
        Object object = delegate.getObject(key);
        if (object == null) {
            // 缓存未命中,则将 key 存入到 entriesMissedInCache 中
            entriesMissedInCache.add(key);
        }

        if (clearOnCommit) {return null;} else {return object;}
    }

    @Override
    public void putObject(Object key, Object object) {
        // 将键值对存入到 entriesToAddOnCommit 这个 Map 中中,而非实在的缓存对象 delegate 中
        entriesToAddOnCommit.put(key, object);
    }

    @Override
    public Object removeObject(Object key) {return null;}

    @Override
    public void clear() {
        clearOnCommit = true;
        // 清空 entriesToAddOnCommit,但不清空 delegate 缓存
        entriesToAddOnCommit.clear();}

    public void commit() {
        // 依据 clearOnCommit 的值决定是否清空 delegate
        if (clearOnCommit) {delegate.clear();
        }
        
        // 刷新未缓存的后果到 delegate 缓存中
        flushPendingEntries();
        // 重置 entriesToAddOnCommit 和 entriesMissedInCache
        reset();}

    public void rollback() {unlockMissedEntries();
        reset();}

    private void reset() {
        clearOnCommit = false;
        // 清空集合
        entriesToAddOnCommit.clear();
        entriesMissedInCache.clear();}

    private void flushPendingEntries() {for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
            // 将 entriesToAddOnCommit 中的内容转存到 delegate 中
            delegate.putObject(entry.getKey(), entry.getValue());
        }
        for (Object entry : entriesMissedInCache) {if (!entriesToAddOnCommit.containsKey(entry)) {
                // 存入空值
                delegate.putObject(entry, null);
            }
        }
    }

    private void unlockMissedEntries() {for (Object entry : entriesMissedInCache) {
            try {
                // 调用 removeObject 进行解锁
                delegate.removeObject(entry);
            } catch (Exception e) {log.warn("...");
            }
        }
    }

}

存储二级缓存对象的时候是放到了 TransactionalCache.entriesToAddOnCommit 这个 map 中,然而每次查问的时候是间接从 TransactionalCache.delegate 中去查问的,所以这个二级缓存查询数据库后,设置缓存值是没有立即失效的,次要是因为间接存到 delegate 会导致脏数据问题

为何只有 SqlSession 提交或敞开之后?

那咱们来看下 SqlSession.commit()办法做了什么

SqlSession

@Override
public void commit(boolean force) {
    try {
        // 次要是这句
        executor.commit(isCommitOrRollbackRequired(force));
        dirty = false;
    } catch (Exception e) {throw ExceptionFactory.wrapException("Error committing transaction.  Cause:" + e, e);
    } finally {ErrorContext.instance().reset();}
}
 
// CachingExecutor.commit()
@Override
public void commit(boolean required) throws SQLException {delegate.commit(required);
    tcm.commit();// 在这里}
 
// TransactionalCacheManager.commit()
public void commit() {for (TransactionalCache txCache : transactionalCaches.values()) {txCache.commit();// 在这里
    }
}
 
// TransactionalCache.commit()
public void commit() {if (clearOnCommit) {delegate.clear();
    }
    flushPendingEntries();// 这一句
    reset();}
 
// TransactionalCache.flushPendingEntries()
private void flushPendingEntries() {for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
        // 在这里真正的将 entriesToAddOnCommit 的对象一一增加到 delegate 中,只有这时,二级缓存才真正的失效
        delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {if (!entriesToAddOnCommit.containsKey(entry)) {delegate.putObject(entry, null);
        }
    }
}
二级缓存的刷新

咱们来看看 SqlSession 的更新操作

public int update(String statement, Object parameter) {
    int var4;
    try {
        this.dirty = true;
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        var4 = this.executor.update(ms, this.wrapCollection(parameter));
    } catch (Exception var8) {throw ExceptionFactory.wrapException("Error updating database.  Cause:" + var8, var8);
    } finally {ErrorContext.instance().reset();}

    return var4;
}

public int update(MappedStatement ms, Object parameterObject) throws SQLException {this.flushCacheIfRequired(ms);
    return this.delegate.update(ms, parameterObject);
}

private void flushCacheIfRequired(MappedStatement ms) {
    // 获取 MappedStatement 对应的 Cache,进行清空
    Cache cache = ms.getCache();
    //SQL 需设置 flushCache="true" 才会执行清空
    if (cache != null && ms.isFlushCacheRequired()) {this.tcm.clear(cache);
    }
}

MyBatis 二级缓存只实用于不常进行增、删、改的数据,比方国家行政区省市区街道数据。一但数据变更,MyBatis 会清空缓存。因而二级缓存不适用于常常进行更新的数据。

总结:

在二级缓存的设计上,MyBatis 大量地使用了装璜者模式,如 CachingExecutor, 以及各种 Cache 接口的装璜器。

  • 二级缓存实现了 Sqlsession 之间的缓存数据共享,属于 namespace 级别
  • 二级缓存具备丰盛的缓存策略。
  • 二级缓存可由多个装璜器,与根底缓存组合而成
  • 二级缓存工作由 一个缓存装璜执行器 CachingExecutor 和 一个事务型预缓存 TransactionalCache 实现
正文完
 0