目录

  • 开撕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;@Datapublic 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中

//1public void addMappers(String packageName) {    addMappers(packageName, Object.class);}//2public 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);    }  }//3public <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、构建MappedStatementprivate void buildStatementFromContext(List<XNode> list) {    if (configuration.getDatabaseId() != null) {      buildStatementFromContext(list, configuration.getDatabaseId());    }    // 构建MappedStatement    buildStatementFromContext(list, null);  }//2、专门用来解析MappedStatementprivate 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;{})

@Overridepublic 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);  }//2public <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
// CachingExecutorpublic <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

@Overridepublic 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()@Overridepublic 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 实现