106-Mybatis的底层原理

Mybatis的底层原理

之前说明的都是Mybatis的应用(61章博客开始(包括其后面的相关博客)),现在来说明他为什么可以那样做
我们继续分析分析JDBC操作的问题:
首先给出具体sql语句:
链接:https://pan.baidu.com/s/1KVIkn4A48tJbkposXGiBLA
提取码:alsk
实体类:
package com;

/**
 *
 */
public class User {
    Integer id;
    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 + '\'' +
                '}';
    }
}

依赖:
  <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.8</version>
        </dependency>
    </dependencies>
测试类:
package com;

import java.sql.*;

/**
 *
 */
public class Mybatis {
    public static void main(String[] args) throws Exception {
        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", "123456");
            // 定义sql语句?表示占位符
            String sql = "select * from user where username = ?";
            // 获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
            preparedStatement.setString(1, "tom");
            // 向数据库发出sql执⾏查询,查询出结果集
            resultSet = preparedStatement.executeQuery();
            // 遍历查询结果集
            User user = new User();
            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();
                }
            }
        }
    }
}

首先说明jdbc的问题:
1:数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能
2:Sql语句连接信息或者sql语句(他们简称为sql)在代码中有硬编码,造成代码不易维护,实际应用中sql变化的可能较大,sql变动需要改变java代码,比如说,如果使用配置文件存放,那么若代码有自动识别是否修改而改变的,那么改动即可,若没有,那么他一个配置是可以给多个类使用的,即我们只需要改动一次即可部署完毕,而由于一般我们都只会提交class文件,原来的还要继续编译(找源代码),而不能直接的运行,即从上不难看出解决硬编码问题的好处
3:使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能 多也可能少,修改sql还要修改代码,系统不易维护(也可以归属于sql语句)
4:对结果集解析存在硬编码(如查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便
问题解决思路:
1:使用数据库连接池初始化连接资源
2:将sql语句抽取到xml配置文件中,注意:这里最好将sql语句和sql连接信息分开,虽然可以在一个配置文件中,但是由于sql会变动很大,可能造成在自动识别中需要识别更多的信息,虽然其他方面没有问题,但是也是一个解决的好处,除了这样且也便于维护
3:使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射,内省:Java内省机制是指通过标准的get/set方法(利用反射调用成员方法)对成员属性进行操作(实际上也是反射,只是对这个操作进行另外一种称呼而已),而不是直接通过反射对属性操作,在Mybatis的sql语句中,就使用到了这个(说明set时就是这样,如类似的set后面名称可以忽略大小写等等,所以是利用了反射,要不然怎么判断忽略呢,当然,这里我们不操作这种情况,所以这里了解即可,所以配置文件中的名称最好要对应)
为了完成上面的要求,现在我们来自定义框架(部分)
自定义框架设计:
使用端(也就是项目):
提供核心配置文件:
sqlMapConfig.xml:存放数据源信息,并引入Mapper.xml
Mapper.xml:sql语句的配置文件信息
注意:上面配置文件名称是可以随便写的,只是为了对标Mybatis来弄成这样而已,让你更加的理解Mybatis的工作流程
框架端(可以认为我们编写的框架,或者说jar包):
1:读取配置文件
一般来说读取完成以后以流的形式存在(getResourceAsSteam(配置文件路径),配置文件是sqlMapConfig.xml),但是我们不能将读取到的配置信息以流的形式存放在内存中,因为不好操作,所以我们可以提取将他们的信息来进行保存好(当然,提取步骤在后面的解析配置文件中说明),即先进行操作完毕,而不是在使用时进行提取,那么我们可以创建javaBean来存储
第一个javaBean:Configuration,用来存放数据库基本信息(一般是sqlMapConfig.xml的信息)和Map<唯一标识,Mapper> ,唯一标识:namespace + “.” + id
第二个javaBean:MappedStatement(Mapper的简称,一般是Mapper.xml的信息),主要是操作sql语句、statement类型(一般是操作标识,即定位的,当然大多数是代表本身,比如select,delete等等的id属性)、输入参数java类型、输出参数java类型等等
2:解析配置文件
创建sqlSessionFactoryBuilder类:
方法:sqlSessionFactory build(流参数):
第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement中
第二:创建SqlSessionFactory的实现类DefaultSqlSession
3:SqlSessionFactory:
方法:openSession(),来获取sqlSession接口的实现类实例对象
4:sqlSession接口及实现类:主要封装crud方法,可以任务是会话对象,即代码平台(或者说语句执行平台)
方法:selectList(String statementId,Object param):查询所有
selectOne(String statementId,Object param):查询单个
具体实现:封装JDBC完成对数据库表的查询操作
涉及到的设计模式:
Builder构建者设计模式、工厂模式、代理模式
上面具体流程只是大致说明完毕,具体细节建议看后面的代码就行,实际上可以看出我们就是来完成Mybatis的底层操作,现在开始实现:
在使用端项目中创建配置配置文件
创建 sqlMapConfig.xml(资源文件夹下):
<configuration> 
<!--数据库连接信息-->
 <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
 <property name="jdbcUrl" value="jdbc:mysql:///mybatis"></property>
    <!--一般在mysql中,不用ip和端口,默认是localhost:3306,所以这里的///就是//localhost:3306/,就是这样,其他的可能不好,mysql一般都会操作的-->
 <property name="user" value="root"></property>
 <property name="password" value="123456"></property>
 <!--引入sql配置信息-->
 <mapper resource="mapper.xml"></mapper>
</configuration> 
mapper.xml:
<mapper namespace="User"> <!--这里之所以加上User,是防止其他xml中有同样的selectOne或者selectList,所以这里是识别唯一xml的,所以需要加上User-->
    <select id="selectOne" paramterType="com.User" resultType="com.User">
        select * from user where id = #{id} and username =#{username}
        <!--id最好就是id,因为我们并没有操作什么大小写,或者get和set的操作,这里主要是类似于aClass.getDeclaredField的方法,他是必须-要对应参数存在的,否则报错,并且我们也没有在之前进行设置忽略的相关操作(比如可以操作try的不报错的循环判断,循环退出后,再考虑是否存在,从而考虑报错)->
    </select>
    <select id="selectList" resultType="com.User">
        select * from user
    </select>
</mapper>
<!--通过上面的说明,所以sql的唯一标识一般是User.selectOne,或者User.selectList-->
<!--resultType就是记录实体与表进行属性与字段的自动映射
这里我们给出主要条件:数据库信息(sqlMapConfig.xml),sql语句信息(mapper.xml),sql语句与参数关联(paramterType),sql语句与类的关联(resultType),这里resultType就是对应的sql语句与类的关联
而我们操作的所有类,都是讲这些数据进行实现,来解决硬编码问题
-->
上面我们并没有操作什么约束,因为我们只是测试而已,所以只要符合xml格式即可,这里建议将mapper修改成UserMapper,这样可以有效的知道是服务于那个实体类的,有利于维护,那么对应的sqlMapConfig配置文件中对应位置也记得要修改
至此,我们与定义的读取配置文件的数据都操作完毕,现在,我们主要从后端代码中拿取这些数据了
User实体(与之前一样):
package com;

/**
 *
 */
public class User { //记得与数据库中的表对应
    Integer id;
    String username;
    String password;
    String birthday;

    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;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getBirthday() {
        return birthday;
    }

    public void setBirthday(String birthday) {
        this.birthday = birthday;
    }

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

再创建一个Maven工程(与原来的同级,当然是子项目也行,这里简称为框架项目)并且导入需要用到的依赖坐标(主要在以后作为jar包来使用的)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mybatis</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>myba</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.17</version>
        </dependency>

        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>


        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>

        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>

        <dependency>
            <!--42章博客有使用到他,"XPath 是一门在 XML 文档中查找信息的语言,可以使用xpath查找xml中的内容,由于DOM4J在解析XML时只能一层一层解析,所以当XML文件层数过多时使用会很不方便,结合XPATH就可以直接获取到某个元素"-->
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>

</project>
Resources类(包路径记得在里面看):
package com.Res;

import java.io.InputStream;

/**
 *
 */
public class Resources {

    //这里是用来读取配置文件的,所以这里定义一个读取配置文件的方法
    public static InputStream getResourceAsSteam(String path) {
        //通过类加载器来使用对应的方法,得到输入流(加载器有顶端出现class,而顶端是c的代码,所以不用怀疑不能)
        //默认是classpath路径,也就是项目路径,或者说资源文件夹里面的以及java(文件夹)里面的
        //但要注意的是,如果对应的项目在当前idea,就算你安装了,那么首先也是使用他的,所以在实际操作中,记得去除掉对应的框架项目,使得不在当前idea中,当然,你可以另外创建一个项目在其他窗口也行
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }

}

现在我们可以在主项目中进行测试:
但是首先我们需要导入依赖包:
  <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        <dependency>
            <!--无论是否是子,都需要,只是父的操作,子也会进行而已(父则相反),只是聚合的作用-->
            <groupId>org.example</groupId>
            <artifactId>test</artifactId>
            <version>1.0-SNAPSHOT</version>
            <!--这里可以选择不打包,因为maven当前显示中可以直接调用的,但是如果不在当前显示,一般需要提取安装,这里了解即可,但是为了保险起见,最好先安装完毕-->
        </dependency>
然后创建test类进行测试:
package com;

import com.Res.Resources;
import org.junit.Test;

import java.io.InputStream;

/**
 *
 */
public class test {
    @Test
    public void res() {
        //得到配置文件的流,从而可以读取信息
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
    }
}

现在我们回到框架项目,编写如下类:
//上面我们拿取了xml的配置信息,主要是解决硬编码问题了,那么现在开始,将配置信息进行实际操作
//在前面我们知道有四个信息:MappedStatement(Mapper的简称,一般是Mapper.xml的信息):主要是操作sql语句、statement类型(一般是操作标识,即定位的,当然大多数是代表本身)、输入参数java类型、输出参数java类型等等
//所以这里也需要四个参数
package com.pojo;

/**
 *
 */
public class MappedStatement {
    //id(statement类型,比如select,delete等等标签的对应id信息,所以这里最好是String类型)
    private String id; //这里是statement,而不是statementId哦
    //sql语句
    private String sql;
    //输入参数
    private Class<?> paramterType;
    //输出参数
    private Class<?> resultType;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getSql() {
        return sql;
    }

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

    public Class<?> getParamterType() {
        return paramterType;
    }

    public void setParamterType(Class<?> paramterType) {
        this.paramterType = paramterType;
    }

    public Class<?> getResultType() {
        return resultType;
    }

    public void setResultType(Class<?> resultType) {
        this.resultType = resultType;
    }
}

再创建如下:
//Configuration,用来存放数据库基本信息(一般是sqlMapConfig.xml的信息)和Map<唯一标识,Mapper> ,唯一标识:namespace + "."  + id
package com.pojo;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 *
 */
public class Configuration {
    //数据源
    private DataSource dataSource;
    //map集合: key:statementId value:MappedStatement(包含对应的标签哦,因为我们只是扫描一个xml,通过他来得到信息,所以且一般需要多个,既然是存,那么为什么不存在这里呢)
    private Map<String, MappedStatement> mappedStatementMap = new HashMap<String, MappedStatement>();

    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;
    }
}

好了对应的类创建完毕,那么读取配置文件需要的步骤操作完毕,现在开始进行完成解析配置文件,我们继续再框架项目中添加如下的类:
package com.config;

import com.Res.Resources;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.pojo.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

/**
 *
 */
public class XMLConfigerBuilder {
    private Configuration configuration;

    //合成复用,每个操作都给一个类来进行操作,而不是创建一个,主要用于一个类被多人操作的情况
    public XMLConfigerBuilder(Configuration configuration) {
        this.configuration = new Configuration();
    }

    //解析配置文件,封装Configuration类
    public Configuration parseConfiguration(InputStream inputStream) throws Exception {
        Document document = new SAXReader().read(inputStream); //解析XML获取文档对象document
        //<configuation>,getRootElement() 获得根元素,这里的api可以选择到42章博客去查看
        Element rootElement = document.getRootElement();
        //selectNodes(query):得到的是xml根节点下的所有满足 xpath 的节点,参数是Xpath 查询串
        List<Element> propertyElements = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element propertyElement : propertyElements) {
            //attributeValue(…) 获得指定属性名的属性值
            String name = propertyElement.attributeValue("name");
            String value = propertyElement.attributeValue("value");
            //其中Properties类是Hashtable类的子类,该对象主要用于处理属性文件,key和value都是String类型的,即也是集合类的一种
            properties.setProperty(name, value);
        }
        //连接池,使用默认配置,具体在41章博客有说明
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("user"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));
        //填充 configuration
        configuration.setDataSource(comboPooledDataSource);
        //至此,对应的数据源信息都操作完毕,上面之所以要分开,主要是判断getProperty参数里面是否一致的问题,我们先进行添加就不用判断了
        //mapper 部分,这里以多个指向的xml为主,不要认为是操作包的哦
        List<Element> mapperElements = rootElement.selectNodes("//mapper");
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
        for (Element mapperElement : mapperElements) {
            String mapperPath = mapperElement.attributeValue("resource");
            //继续读取,所以也需要一个对于的对象来操作,即xmlMapperBuilder,也自然需要传递输入流
            InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
            xmlMapperBuilder.parse(resourceAsSteam);
        }
        return configuration;
    }
}
需要的对应类:
package com.config;

import com.pojo.Configuration;
import com.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;

/**
 *
 */
public class XMLMapperBuilder {
    private Configuration configuration;

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

    public void parse(InputStream inputStream) throws Exception {
        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类型
            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);

        }

    }

    //得到class类型
    private Class<?> getClassType(String paramterType) throws Exception {
         //这里也要考虑对应是否为null,如果是,那么应该返回null
        if(paramterType==null){
            return null;
        }
        Class<?> aClass = Class.forName(paramterType);
        return aClass;
    }
}

至此,上面的两个类,使得配置文件的信息得以保存,并且放在Configuration里面,现在我们需要如下的类来进行操作:
package com.sqlsession;

import com.config.XMLConfigerBuilder;
import com.pojo.Configuration;
import org.dom4j.DocumentException;

import java.beans.PropertyVetoException;
import java.io.InputStream;

/**
 *
 */
public class SqlSessionFactoryBuilder {
    private Configuration configuration;

    public SqlSessionFactoryBuilder() {
        this.configuration = new Configuration();
    }

    public SqlSessionFactory build(InputStream inputStream) throws Exception {
        //1.解析配置文件,封装Configuration,我们可以选择将解析过程写在这里,但是为了好维护,我们创建XMLConfigerBuilder类来进行解析,使得以后可以复用的
        XMLConfigerBuilder xmlConfigerBuilder = new XMLConfigerBuilder(configuration);
        Configuration configuration = xmlConfigerBuilder.parseConfiguration(inputStream);
        //2.创建 sqlSessionFactory,工厂模式,使得主操作不用创建,只需要修改这一处即可,所以正是因为这样,一般来说我们需要一个接口,来完成可以以后的修改,那么自然不能是当前类了
        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return sqlSessionFactory;

    }
}

package com.sqlsession;

/**
 *
 */
public interface SqlSessionFactory {
    public SqlSession openSession();
}

package com.sqlsession;

import java.sql.SQLException;
import java.util.List;

/**
 *
 */
public interface SqlSession {
    public <E> List<E> selectList(String statementId, Object... param) throws Exception;

    public <T> T selectOne(String statementId, Object... params) throws Exception;

    public void close() throws SQLException;
}

package com.sqlsession;

import com.pojo.Configuration;

/**
 *
 */
//因为是实现他,所以才说是工厂接口(接口可以让更多人进行操作,只需要改动类即可,所以一般我们都会将需求称为接口,因为接口必然对应实现类,也是现在的规范),其中我们将解析配置文件的结果configuration传递进来进行操作
//之所以是工厂,是因为如果有其他的实现类(其他的操作,或者就是该类,但是我们添加一些代表补充,虽然补充一般我们会认为是装饰器模式(前提是指向相同方法,且是方法)),那么使用他就行,从而改变所有使用对应的SqlSessionFactoryBuilder类的地方,这也是工厂模式的好处
public class DefaultSqlSessionFactory implements SqlSessionFactory{
    private Configuration configuration;
    public DefaultSqlSessionFactory(Configuration configuration) {

        this.configuration = configuration;
    }
    //被返回后他也有方法可以操作,简单来说前面根据SqlSessionFactoryBuilder操作的配置文件后,返回的一个类就是这个,而他是工厂操作,那么自然每个工厂返回的类有他自己的类操作
    //很明显,他在满足工厂模式的情况下,进一步返回操作封装的configuration,其实,如果可以,他可以返回自身,而自身继续操作,但是这样没有满足工厂的,也就没有什么扩展可言
   	
    //这里的方法,又实现工厂模式,大多数我们并不会在主操作中操作new,所以我们通常这样操作
    @Override
    public SqlSession openSession(){
        return new DefaultSqlSession(configuration);
    }
}

实际上工厂模式除了将创建对象给方法外,还有一个最为重要的操作,即可以将操作分开,使得一个类不会有多个操作,这也是为什么类也有分工的原因,虽然大多数不是使用工厂来区分的,即工厂的核心还是没有在主操作中进行创建对象
这里就是主要的操作了:
package com.sqlsession;

import com.pojo.Configuration;
import com.pojo.MappedStatement;

import java.sql.SQLException;
import java.util.List;

/**
 *
 */
public interface Executor {
    <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws Exception;

    void close() throws SQLException;

}

package com.sqlsession;

import com.pojo.Configuration;
import com.pojo.MappedStatement;

import java.sql.SQLException;
import java.util.List;

/**
 *
 */
public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;


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

    //处理器对象,专门来操作具体sql执行的,也就是jdbc代码,这里并不会用来返回给任何人,所以直接使用他的方法即可
    private Executor simpleExcutor = new SimpleExecutor();

    //对应工厂返回的对象类中,都有各自的操作,当然,这里一般只有一个工厂(大多都是这样,只是使用模式而已,总不能没有扩展吧),对应的类都也操作工厂模式


    @Override
    //由于我们不知道对应的类型是什么,所以这里利用泛型方法来表示对应的类型(变成对应的类型,包括返回值的泛型和参数的泛型)
    public <E> List<E> selectList(String statementId, Object... param) throws Exception {
        //这里我们传递对应的标识,既然我们知道对应的配置信息,那么要执行这个,必然是需要找到对应的sql语句的,并需要利用配置文件的数据库配置信息
        //所以这里的statementId就是对应的namespace.id,而Object... param可以认为是条件(因为条件的多个的),且可以是类,所以设为Object的可变长参数
        //当然,对应的由于是操作类,所以这里以一个类为主,即这个参数主要考虑paramterType的名称的值

        //在map中拿取指定的标签的信息,即通过我们传递的statementId可以得到对应的信息,因为
        /*
        //statementId
        String key = namespace + "." + id;
         */
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        List<E> query = simpleExcutor.query(configuration, mappedStatement, param);
        return query;
    }

    @Override
    //selectOne 中调用 selectList
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<Object> objects = selectList(statementId, params);
        if (objects.size() == 1) {
            return (T) objects.get(0);
        } else if (objects.size() == 0) {
            throw new RuntimeException("没有结果");
        } else {
            throw new RuntimeException("返回结果过多");
        }
    }

    @Override
    public void close() throws SQLException {
        simpleExcutor.close();
    }
}

很明显上面的主要操作就是simpleExcutor的操作,所以我们看看这个类:
package com.sqlsession;

import com.pojo.Configuration;
import com.pojo.MappedStatement;
import com.utils.GenericTokenParser;
import com.utils.ParameterMapping;
import com.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 *
 */
public class SimpleExecutor implements Executor {
    private Connection connection = null;

    //操作jdbc代码,我们之前的配置最终都需要这样的操作,只是需要获取或者保存一系列的信息而已以及一些设计模式的使用
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws Exception {
        //获取连接
        connection = configuration.getDataSource().getConnection();
        // select * from user where id = #{id} and username = #{username}
        String sql = mappedStatement.getSql();
        //对sql进行处理,由于对应的sql中存在#{}所以需要进行变化,且之所以没有操作?,是因为若是实体类,我们需要根据里面的信息来进行替换的,这也是为什么需要使用#{}的一个原因
        //得到最终数据的结果,即?位置及其数据,替换了?哦
        BoundSql boundsql = getBoundSql(sql);
        // 得到select * from where id = ? and username = ?
        String finalSql = boundsql.getSqlText();
        //获取传入参数类型,mappedStatement可是保存我们定位到的标签信息哦
        Class<?> paramterType = mappedStatement.getParamterType();
        //获取预编译preparedStatement对象,他是操作?的,这也是为什么我们使用替换?的一个原因
        PreparedStatement preparedStatement = connection.prepareStatement(finalSql);
        //得到?的位置及其数据替换(如id等等)
        List<ParameterMapping> parameterMappingList = boundsql.getParameterMappingList();
        //由于这个大小的存在,所以如果没有参数,他自然不会执行,也就不会考虑paramterType是否为null的情况了
        //因为没有参数,他自然为0,因为每有一个?他都会添加
        for (int i = 0; i < parameterMappingList.size(); i++) {
            //拿取每一个的ParameterMapping数据,他就包括了?的位置,实际上由于这个循环的作用,那么这个位置也不用操作了,我们只需要将他的对应的替换进行操作即可
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            //得到那个替换数据
            String name = parameterMapping.getContent();
            //反射,注意:这里是操作类的,所以这里并没有其他的判断,这里要注意
            //Field getDeclaredField(String name),用于获取此Class对象所表示类中参数指定的单个成员变量信息
            //由于这里操作入参,因为?是替换的,所以获取该成员信息
            Field declaredField = paramterType.getDeclaredField(name); //这之前,我们并不操作set,只是操作成员变量而已
            //void setAccessible(booleanflag),当实参传递true时,则反射对象在使用时应该取消 Java 语言访问检查,即保证修改成功
            declaredField.setAccessible(true);
            //参数的值,因为是类,所以这里直接为0,因为只有一个
            //Object get(Object obj),获取参数对象obj中此Field对象所表示成员变量的数值
            Object o = declaredField.get(param[0]);
            //实际上这里之所以利用反射,是保证入参类型与传递的参数类型一致,所以如果不一致,那么通常可能会报错
            //也就是说Object o = declaredField.get(param[0]);会发生报错,所以利用反射也是一种防止手段,那么有个问题,可以自己判断吗
            //答:不能,因为上面的Object类型的,你怎么知道他的类是啥呢,所以这里利用反射来确定一致性
            //给占位符赋值
            preparedStatement.setObject(i + 1, o);
        }
        //上面就完成了设置参数,现在我们需要执行
        //因为都是select,所以操作executeQuery方法,当然,这里只是测试,如果是多个的话,我们一般是需要判断的
        ResultSet resultSet = preparedStatement.executeQuery();
        //获得返回的类型
        Class<?> resultType = mappedStatement.getResultType();
        ArrayList<E> results = new ArrayList<E>(); //因为可以得到多行数据,或者说列数据(对于结果来说),所以需要这个集合
        //当然,由于我们并没有操作其他的标签属性来决定是否操作集合,所以这里统一操作集合
        while (resultSet.next()) { //进行遍历
            //PrepareStatement 预处理对象调用 getMetaData () , 获取的是ResultSetMetaData , 结果集元数据对象
            //他(ResultSetMetaData metaData)包含如下方法:
            /*
            //getColumnCount() : 当前结果集共有多少列,注意,这里代表的是结果,也就是说代表一行一行的数据
            //getColumnName(int i) : 获取指定列号的列名, 参数是整数 从1开始
            //getColumnTypeName(int i): 获取指定列号列的类型, 参数是整数 从1开始
             */
            ResultSetMetaData metaData = resultSet.getMetaData();
            E e = (E) resultType.newInstance(); //创建定义返回的类,一般情况下,编译期会将这个e可能放在外面来操作,具体如何主要看编译器怎么操作了(可能如果后面没有,放在最外面),这里了解即可
            //getColumnCount() : 当前结果集共有多少列
            int columnCount = metaData.getColumnCount();
            for (int i = 1; i <= columnCount; i++) { //因为上面是从1开始的,所以这里也是哦
                //属性名
                //getColumnName(int i) : 获取指定列号的列名, 参数是整数 从1开始
                String columnName = metaData.getColumnName(i);
                //属性值
                Object value = resultSet.getObject(columnName);
                //很明显,这里我们可以根据上面的属性名来设置前面E类的值(反射,或者说内省),但是这里我们使用另外一种我们没有使用过的技术
                //看如下:
                //创建属性描述器,为属性⽣成读写方法,相当于操作对应的属性
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType);
                //获取写方法
                Method writeMethod = propertyDescriptor.getWriteMethod();
                //向类中写入值
                writeMethod.invoke(e, value);
                /*
                一般情况下,我们会使用这种方式

                Field declaredField = resultType.getDeclaredField(columnName);
                declaredField.set(e,value);

               很明显,PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType);
               就相当于
               Field declaredField = resultType.getDeclaredField(columnName);
               declaredField.setAccessible(true);
               declaredField.set(e,value);
                只是里面自定义了declaredField	.set(e,value);的封装方法,使得进行操作,我们可以看到,使用原来的应该要好点,那么为什么使用这个呢,或者说好处是什么
                首先他没有什么null的问题,因为他不是resultType来调用的,这就是主要的好处

                 */
            }
            results.add(e);
        }
        return results;
    }


    @Override
    public void close() throws SQLException {
        //SqlSession的close最终还是连接的close关闭操作
        connection.close();
    }


    //完成#{}的替换,然后将真的数据进行保存并返回
    private BoundSql getBoundSql(String sql) {
        //标记处理类:主要是配合通用标记解析器GenericTokenParser类完成对配置文件等的解析工作,其中TokenHandler主要完成处理
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        //GenericTokenParser :通用的标记解析器,完成了代码⽚段中的占位符的解析,然后再根据给定的标记处理器(TokenHandler)来进行表达式的处理
        //三个参数:分别为openToken (开始标记)、closeToken (结束标记)、handler (标记处理器)
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //将sql传递进来进行解析
        String parse = genericTokenParser.parse(sql);
        //这里就是得到?对应的替换信息,其中下标代表?的先后顺序,而类保存的值,代表替换的数据
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        //将解析数据进行保存
        BoundSql boundSql = new BoundSql(parse, parameterMappings);
        return boundSql;
    }
}

package com.sqlsession;

import com.utils.ParameterMapping;

import java.util.ArrayList;
import java.util.List;

/**
 *
 */
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;
    }
}


package com.utils;

/**
 * @author Clinton Begin
 */
public interface TokenHandler {
  String handleToken(String content);
}


package com.utils;

public class ParameterMapping {

    //保存了对应替换的信息,以字符串的形式进行
    private String content;

    public ParameterMapping(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

package com.utils;

import java.util.ArrayList;
import java.util.List;


public class ParameterMappingTokenHandler implements TokenHandler {
    //之所以要这样,是保证添加的顺序,以及信息,所以这样就可以决定谁先操作?了
    private List<ParameterMapping> parameterMappings = new ArrayList();

    // context是参数名称 #{id} #{username}
    @Override
    public String handleToken(String content) {
        parameterMappings.add(buildParameterMapping(content));
        return "?";
    }

    private ParameterMapping buildParameterMapping(String content) {
        ParameterMapping parameterMapping = new ParameterMapping(content);
        return parameterMapping;
    }

    public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
    }

    public void setParameterMappings(List<ParameterMapping> parameterMappings) {
        this.parameterMappings = parameterMappings;
    }

}

后续是mybatis的替换操作

/**
 * Copyright 2009-2017 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.utils;

/**
 * @author Clinton Begin
 */
public class GenericTokenParser {

    private final String openToken; //开始标记
    private final String closeToken; //结束标记
    private final TokenHandler handler; //标记处理器

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

    /**
     * 解析${}和#{}
     *
     * @param text
     * @return 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据
     * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
     */
    //这里一般传递的是sql
    public String parse(String text) {
        // 验证参数问题,如果是null,就返回空字符串。
        if (text == null || text.isEmpty()) {
            return "";
        }

        // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行,也就是不用解析,sql就是sql
        //int indexOf(String str, int fromIndex),表示从字符串的fromIndex位置开始检索str第一次出现的位置
        int start = text.indexOf(openToken, 0);
        if (start == -1) {
            return text;
        }

        // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder
        // text变量中占位符对应的变量名为expression,判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码

        //我们将sql的字符串信息变成字符数组,在xml中得到的都是字符串哦
        char[] src = text.toCharArray();
        //定义起始偏移量
        int offset = 0;
        //使用这个可变的String
        final StringBuilder builder = new StringBuilder();
        StringBuilder expression = null;
        while (start > -1) { //代表存在
            // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
            if (start > 0 && src[start - 1] == '\\') {
                //这个append方法代表将字符数组的offset起始位置,添加start - offset - 1数量的值进行添加,从offset开始
                //然后继续加上openToken,说明拿取了包括#{之前的sql信息
                builder.append(src, offset, start - offset - 1).append(openToken);
                //起始位置发生改变,正好在#{后面一个位置
                offset = start + openToken.length();
            } else {
                //重置expression变量,避免空指针或者老数据干扰
                if (expression == null) {
                    expression = new StringBuilder();
                } else {
                    //setLength()方法用于在将字符序列替换为新字符序列时设置字符序列的长度,因此该序列的长度将由给定参数指定
                    //比如:你有123这个数据,若我设置为2,那么只会显示12,3被清除,所以如果设置为0,代表清空数据,并且如果设置为4,那么多余的用空字符来代表,注意是字符哦,对于基本数据类型来说,超过的则总后面显示
                    expression.setLength(0);
                }
                //继续添加,但是并没有添加#{,主要是为了后面进行替换?
                builder.append(src, offset, start - offset);
                //改变起始位置
                offset = start + openToken.length();
                //再次获得结果,只不过这里以结束标记作为起始,且是以整个test的sql来查找的,一般都是利用test的
                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保留了#{}里面的信息,但是由于我们只会保留一个,所以直接返回
                        expression.append(src, offset, end - offset);
                        offset = end + closeToken.length();
                        break;
                    }
                }
                //如果没有结果,说明没有},所以直接执行如下,后面的就不用判断了,因为没有},自然不用操作替换,即必然会使得后面进行退出
                if (end == -1) {
                    // close token was not found.
                    //得到后面的sql内容
                    builder.append(src, start, src.length - start);
                    offset = src.length;
                } else {
                    //首先根据参数的key(即expression)进行参数处理,返回?作为占位符
                    //其中,handler是封装的,我们执行他的方法,且他的方法也是操作创建类的,这里也基本都利用的工厂模式
                    //总之,只是保存一下而已,而expression就是对应的要替换的信息,因为直接返回了,所以替换了?
                    builder.append(handler.handleToken(expression.toString()));
                }
            }
            //查看后面是否还有#{,从而判断是否继续执行这个循环
            start = text.indexOf(openToken, offset);
        }
        //判断开始标签直接不存在的情况
        if (offset < src.length) {
            builder.append(src, offset, src.length - offset);
        }
        return builder.toString();
    }
    //注意:上面的操作中,你可能会认为是有很多的细节问题的,实际上并没有
    //这个问题在后面进行说明,现在我们先使用这个,当然,那些问题又何尝不是一些小问题呢

    //比如你可以尝试一下#{s\\} && user = #{user},会发现结果很特殊,所以说,如果是规范的那么不会出现这样的特殊问题,这主要是该替换的缺陷而已
    //比如在if (end > offset && src[end - 1] == '\\')里面就有逻辑的问题(不规范的情况下)
    //你可以尝试一下:select * from ee where id = #{s\\} && user = #{user}
    //最终会变成select * from ee where id = ?,而不是select * from ee where id = #{s} && user = ?
    //实际上他是对的,因为#{s\\} && user = #{user}中被包括的是s\\} && user = #{user,他是一个整体,要不然,为什么上面的if (end > offset && src[end - 1] == '\\')里面代表是否存在的问题呢,所以说该问题并不是问题,只是一个不好的操作而已

}

在mybatis的3.5.4版本中,是这样的操作:
      public String parse(String text) {
      if (text != null && !text.isEmpty()) {
            int start = text.indexOf(this.openToken);
            if (start == -1) {
                return text;
            } else {
                char[] src = text.toCharArray();
                int offset = 0;
                StringBuilder builder = new StringBuilder();

                for(StringBuilder expression = null; start > -1; start = text.indexOf(this.openToken, offset)) {
                    if (start > 0 && src[start - 1] == '\\') {
                        builder.append(src, offset, start - offset - 1).append(this.openToken);
                        offset = start + this.openToken.length();
                    } else {
                        if (expression == null) {
                            expression = new StringBuilder();
                        } else {
                            expression.setLength(0);
                        }

                        builder.append(src, offset, start - offset);
                        offset = start + this.openToken.length();

                        int end;
                        for(end = text.indexOf(this.closeToken, offset); end > -1; end = text.indexOf(this.closeToken, offset)) {
                            if (end <= offset || src[end - 1] != '\\') {
                                expression.append(src, offset, end - offset);
                                break;
                            }

                            expression.append(src, offset, end - offset - 1).append(this.closeToken);
                            offset = end + this.closeToken.length();
                        }

                        if (end == -1) {
                            builder.append(src, start, src.length - start);
                            offset = src.length;
                        } else {
                            builder.append(this.handler.handleToken(expression.toString()));
                            offset = end + this.closeToken.length();
                        }
                    }
                }

                if (offset < src.length) {
                    builder.append(src, offset, src.length - offset);
                }

                return builder.toString();
            }
        } else {
            return "";
        }
    }
//对于\\来说,一般无论是#{,还是},只要有一个人存在,那么直接找后面的,虽然有细节问题(但还是合理的)
测试(记得到另外一个窗口项目,与主项目一样,并导入安装的框架项目依赖来操作,或者在框架项目中加上对应的配置文件来操作):
package com;

import com.Res.Resources;
import com.sqlsession.SqlSession;
import com.sqlsession.SqlSessionFactory;
import com.sqlsession.SqlSessionFactoryBuilder;

import java.io.InputStream;

/**
 *
 */
public class a {
    public static void main(String[] args) throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        //读取配置信息,并通过工厂模式返回一个可以操作的类
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        //每个可以操作的类也有方法,且也是工厂模式来返回操作的类
        SqlSession sqlSession = build.openSession();
        User user = new User();
        user.setId(2);
        user.setUsername("tom");

        User o = sqlSession.selectOne("User.selectOne", user);
        System.out.println(o);
        List<Object> list = sqlSession.selectList("User.selectList");
        System.out.println(list);
    }
}
//这里我们要记得一下,一般来说,有些mysql版本可能在mysql中password会被替换成authentication_string,所以注意即可,一般高版本是这样的,这里了解即可,即记得看版本哦,一般8.0好像就被替换了
你可以发现,对应的与前面的我们写的替换操作基本是一样的,只是代码不同顺序而已
尝试细节问题:
package com.utils;

/**
 *
 */
public class m {
    public static void main(String[] args) {
        String a = "select * from ee where id = #{s\\} && user = #{user}";
        String parse = new GenericTokenParser("#{", "}", new ParameterMappingTokenHandler()).parse(a);
        System.out.println(parse); //select * from ee where id = ?

        //String a = "select * from ee where id = #{s && user = #{user}";也是
    }
}
//注意:他是对的
实际上前面也操作了工厂模式,当然,我并没有指出,一般情况下,没有在主操作中创建的对象,我们都会认为是工厂模式,但是工厂模式一定好吗,其实我们可以发现,工厂模式虽然看起来好,但是最终类或者接口实在太多,也就是说,如果死板的按照设计模式来编写,在维护上,即在一定程度上,反而降低了维护效率,所以有时候,我们会直接的使用对应的方法,而不是返回出现,即定义成员变量或者静态变量等等,所以设计模式(不只是工厂模式哦,其他也算)一般也需要平衡类的多少或者存在的合理来进行设计,而不是看到一个类就使用设计模式
到这里mybatis的原理基本说明完毕,当然了,上面的功能或者细节,远不及真的mybatis,因为对于mybatis来说,他的标签可是非常多了,我们远不及他,因为mybatis可不是一人就能开发完毕的,就算可以,也是需要非常多的时间,因为就算你跟着他的源码敲也需要非常多的时间,更不要说自己完成了
当然,上面的细节问题,我们了解即可,并不需要修改,因为一般我们都会规范的写的,且他是对的
最后,要说明一下如下操作:
//Field的setAccessible(true)方法
//用来满足私有的权限,并且满足不在同一个包下(其中若对应的类是当前包下的子包,也算)
//简单来说,如果对应的类的某个属性设置了私有,或者该类没有在你当前的包下,或者在当前包下的其他包下,那么就会不能访问,即报错,所以需要这个

//还有一点,如果maven没有操作包,那么可能直接在java资源文件夹下的类,应该是不能导入的,或者说,不能操作,这一般是maven的问题,在springboot中,一般也有这样的问题,这可能是一个规定,具体可以百度
自定义框架优化:
通过上述我们的自定义框架,我们解决了JDBC操作数据库带来的一些问题:例如频繁创建释放数据库连 接,硬编码,⼿动封装返回结果集和参数等问题,但是现在我们继续来分析刚刚完成的自定义框架代码,有没 有什么问题?
问题如下:
这里的dao看后面的代码:
dao的实现类(认为的我们操作的流程,看后面的实现类即可)中存在重复的代码,整个操作的过程模板重复(读取配置文件,创建SqlSessionFactory,创建sqlsession)
dao的实现类中存在硬编码,调用sqlsession的方法时,参数statement的id(简称statementId)硬编码需要我们进行编写
解决:使用代理模式来创建接口的代理对象:
现在我们来进行操作,首先,就是在主项目中,添加如下的接口和类(包自己创建):
package com.dao;

import com.User;

import java.util.List;

/**
 *
 */
public interface IUserDao {
    //查询所有用户
    public List<User> findAll();

    //根据条件进行用户查询
    public User findBy(User user);
}

package com.dao;

import com.Res.Resources;
import com.User;
import com.sqlsession.SqlSession;
import com.sqlsession.SqlSessionFactory;
import com.sqlsession.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
 *
 */
public class IUserDaoImpl implements IUserDao {
    @Override
    public List<User> findAll() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        //读取配置信息,并通过工厂模式返回一个可以操作的类
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        //每个可以操作的类也有方法,且也是工厂模式来返回操作的类
        SqlSession sqlSession = build.openSession();
        List<User> list = sqlSession.selectList("User.selectList");
        System.out.println(list);
        return list;
    }

    @Override
    public User findBy(User user) throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        //读取配置信息,并通过工厂模式返回一个可以操作的类
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        //每个可以操作的类也有方法,且也是工厂模式来返回操作的类
        SqlSession sqlSession = build.openSession();
        User o = sqlSession.selectOne("User.selectOne", user);
        System.out.println(o);
        return o;

    }
}

很明显,上面我们若要操作一个执行,必然会有重复的操作,而由于大多数我们并不会操作一个方法里面,所以有重复问题:
 InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        //读取配置信息,并通过工厂模式返回一个可以操作的类
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        //每个可以操作的类也有方法,且也是工厂模式来返回操作的类
        SqlSession sqlSession = build.openSession();
所以我们需要进行提取,当然,实际上虽然他封装的,但是你调用他们还是执行的,即还是存在很多代码(这里包括mybatis中的接口操作,所以一般其只是操作包地址来解决后面的标识,而不是用方法包括而已,因为方法的名称是固定的,而他们不是)
其中statementId存在硬编码,当然,对于读取配置文件来说,必然需要存在的,因为代码直接总要有个联系吧,而我们只能降低联系,那么我们也要就解决这个硬编码,那么如何解决呢,我们使用代理来解决
对于提取来说,并不需要说明,但是这个硬编码我们需要进行操作,现在,我们将实现类删除,在框架项目的SqlSession中加上如下:
//为Dao接口生成代理实现类
    public <T> T getMapper(Class<?> mapperClass);
在DefaultSqlSession实现类中加上如下:
 @Override
    public  <T> T getMapper(Class<?> mapperClass) {
        //使用JDk动态代理,来为Dao接口生成代理对象,并返回
        /*
        Proxy.newProxyInstance()方法的参数介绍:
        ClassLoader loader,
        类加载器,反射基本也是可以是类加载器来操作的(他可以操作Class,一般也有其他功能,所以这里是类加载器),这里可以借助被代理对象获取到类加载器
        由于大多数的类加载器是一样的,所以这个参数基本可以随便使用某个类的加载器
        Class<?>[] interfaces,
        被代理类所需要实现的全部接口(如果给出的接口是对方没有的,那么会报错,一般单纯的打印代理类也会执行一次,而不用是执行对应的接口,这是一个特别的地方,实际上是因为默认public java.lang.String java.lang.Object.toString()(对应的method打印信息),使得的,也就是为什么打印本身会操作,即的确是一个特殊的地方(因为他是类,注意即可),所以也最好不要在里面再次打印proxy,否则会一直循环,使得触发jvm结束的,具体为什么可以百度),所以是数组,注意是接口的Class类型哦,一般这里代表返回值类型(因为代理类与他相关,实现他)
        InvocationHandler h
        当代理对象调用接口中的任意方法时,那么都会执行InvocationHandler的invoke方法
         */
        Object o = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 得到接口方法的名称
                String methodName = method.getName();
                // 得到接口方法当前接口(method.getDeclaringClass())的名称(getName())
                String className = method.getDeclaringClass().getName();
                //statementid
                String key = className + "." + methodName;
                //得到对应statementid的信息
                MappedStatement mappedStatement = configuration.getMappedStatementMap().get(key);
                //得到接口方法的返回值类型
                Type genericReturnType = method.getGenericReturnType();
                //判断是否实现泛型类型参数化,也就是说,如果返回值类型有泛型,那么返回true
                if (genericReturnType instanceof ParameterizedType) {
                    return selectList(key, args);

                }
                return selectOne(key, args); //只要进入这里,那么返回值就是他,也就是说,只要被拦截,那么你执行的结果返回值就是这个
            }
        });
        return (T) o;
    }
//很明显,上面是将接口的路径(全限定名,是在项目的路径)和名称作为配置文件的对应的statementid,其中我们并不操作<package name来决定找到以配置文件来进行读取,使得配置文件一般也需要在接口所在的路径下,所以这里的配置可以不用在接口的路径下,只要对应的namespace或者id对应即可

//这里要注意,由于自身能打印(toString也会进行触发,所以对应的invoke里面的参数不要写当前代理类(他也是存在对应方法的),否则是会无限循环的)

现在在另外一个窗口项目中,修改如下(记得同步主项目的接口或者类):
package com;

import com.Res.Resources;
import com.dao.UserMapper;
import com.sqlsession.SqlSession;
import com.sqlsession.SqlSessionFactory;
import com.sqlsession.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
 *
 */
public class a {
    public static void main(String[] args) throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        //读取配置信息,并通过工厂模式返回一个可以操作的类
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        //每个可以操作的类也有方法,且也是工厂模式来返回操作的类
        SqlSession sqlSession = build.openSession();
        User user = new User();
        user.setId(2);
        user.setUsername("tom");

//        User o = sqlSession.selectOne("User.selectOne", user);
//        System.out.println(o);
//        List<Object> list = sqlSession.selectList("User.selectList");
//        System.out.println(list);
        //因为是泛型方法,所以不会考虑是否在编译期判断(他是自动的,所以一般不会考虑,单纯的泛型会认为是Object哦)
        //即这里就算是User被接收也不会报错,但是运行报不报错就不知道了
        //这里是JDk动态代理,接收了对应接口的代理类,并且,当对应的接口方法被执行时,会执行第一个参数中的方法,从而完成这个类的操作
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //那么这个好处是什么,首先,因为jdk代理可以在对应的拦截中进行补充操作,而这个补充就可以操作解决硬编码问题,这个补充就是在硬编码问题上操作对应的真的方法(selectList,selectOne)
        //也可以操作重复问题,但是一般我们不会操作重复问题,因为对应的配置文件是会改变的,由于这里只是sqlSession来操作,所以并没有操作上面的重复,因为我们提取出来即可
        //代理对象Proxy调用接口中的任意方法时,都会执行底层的invoke方法,从而实现增强,而这个增强就是解决硬编码问题的
        List<User> all = mapper.findAll();
        System.out.println(all);
        User by = mapper.findBy(user);
        System.out.println(by);
    }
}

且对应的配置文件信息记得对应哦,这样我们测试后,才不会出现错误
至此,我们的问题也解决完毕了
现在我们来补充添加,删除,修改等等操作
首先我们在框架项目XMLMapperBuilder中添加或者修改如下:
public void in(String namespace, List<Element> list, Configuration configuration) throws Exception {
        //foreach不能操作null,否则报错,所以这里这样操作
        if(list!=null) {
            for (Element element : list) { //id的值
                String id = element.attributeValue("id");
                String paramterType = element.attributeValue("paramterType");
                String resultType = element.attributeValue("resultType");
                //输入参数class,既然对应的输入和输出是一个具体的类型,且是全路径,自然需要直接得到对应的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);

            }
        }
    }


 public void parse(InputStream inputStream) throws Exception {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");
        List<Element> select = rootElement.selectNodes("select");
        in(namespace, select, configuration);
        List<Element> insert = rootElement.selectNodes("insert");
        in(namespace, insert, configuration);
        List<Element> delete = rootElement.selectNodes("delete");
        in(namespace, delete, configuration);
        List<Element> update = rootElement.selectNodes("update");
        in(namespace, update, configuration);

    }
这样我们可以保存对应的添加,删除,修改,查询的信息
然后我们在SqlSession中添加如下:
//因为这三个基本只会返回int,不会考虑是什么类型的,所以这里直接用int即可,那么同样的,也不用在配置文件中操作返回的类型
    public int insert(String statementId, Object... params) throws Exception;

    public int delete(String statementId, Object... params) throws Exception;

    public int update(String statementId, Object... params) throws Exception;

首先我们在SimpleExecutor中添加如下:
public int update(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws Exception {
        connection = configuration.getDataSource().getConnection();
        String sql = mappedStatement.getSql();

        BoundSql boundsql = getBoundSql(sql);

        String finalSql = boundsql.getSqlText();

        Class<?> paramterType = mappedStatement.getParamterType();

        PreparedStatement preparedStatement = connection.prepareStatement(finalSql);

        List<ParameterMapping> parameterMappingList = boundsql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String name = parameterMapping.getContent();

            Field declaredField = paramterType.getDeclaredField(name); //这之前,我们并不操作set,只是操作成员变量而已
            declaredField.setAccessible(true);

            Object o = declaredField.get(param[0]);

            preparedStatement.setObject(i + 1, o);
        }
        int i1 = preparedStatement.executeUpdate();

        return i1;
    }
然后在Executor中添加如下:
    public int update(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws Exception;
然后在DefaultSqlSession中添加如下:
@Override
    public int insert(String statementId, Object... params) throws Exception {
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        int update = simpleExcutor.update(configuration, mappedStatement, params);
        return update;
    }

    @Override
    public int delete(String statementId, Object... params) throws Exception {
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        int update = simpleExcutor.update(configuration, mappedStatement, params);
        return update;
    }

    @Override
    public int update(String statementId, Object... params) throws Exception {
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        int update = simpleExcutor.update(configuration, mappedStatement, params);
        return update;
    }
这样我们来进行测试,在另外一个窗口项目中进行测试:
首先在配置文件中加上如下:
   <insert id="insert" paramterType="com.User">
        insert into user values(#{id},#{username},#{password},#{birthday})
    </insert>
    <delete id="delete" paramterType="com.User">
        delete from user where id = #{id}
    </delete>

    <update id="update" paramterType="com.User">
        update user set username = #{username} where id = #{id}
    </update>
然后进行测试:
package com;

import com.Res.Resources;
import com.sqlsession.SqlSession;
import com.sqlsession.SqlSessionFactory;
import com.sqlsession.SqlSessionFactoryBuilder;

import java.io.InputStream;

/**
 *
 */
public class b {
    public static void main(String[] args) throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        //读取配置信息,并通过工厂模式返回一个可以操作的类
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        //每个可以操作的类也有方法,且也是工厂模式来返回操作的类
        SqlSession sqlSession = build.openSession();
        User user = new User();
        user.setId(3);
        user.setUsername("m");
        int insert = sqlSession.insert("com.dao.UserMapper.insert", user);
        System.out.println(insert);
        int update = sqlSession.update("com.dao.UserMapper.update", user);
        System.out.println(update);
        int delete = sqlSession.delete("com.dao.UserMapper.delete", user);
        System.out.println(delete);
    }
}

测试若没有问题,那么我们来操作对应的代理模式:
在这之前,我们首先需要修改MappedStatement类的属性,即添加对应标签的类型,添加如下属性:
 private String qian;

    public String getQian() {
        return qian;
    }

    public void setQian(String qian) {
        this.qian = qian;
    }
然后在XMLMapperBuilder类里面修改如下:
 MappedStatement mappedStatement = new MappedStatement();
                mappedStatement.setId(id);
                mappedStatement.setParamterType(paramterTypeClass);
                mappedStatement.setResultType(resultTypeClass);
                mappedStatement.setSql(textTrim);
                mappedStatement.setQian(element.getName()); //这里进行操作,即添加当前标签的类型,如果是select,这里就保存select
现在我们到DefaultSqlSession里进行修改如下:
//getMapper方法里面的  
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(key);
                if (configuration.getMappedStatementMap().get(key).getQian() == "select") {
                    //得到接口方法的返回值类型
                    Type genericReturnType = method.getGenericReturnType();
                    //判断是否实现泛型类型参数化,也就是说,如果返回值类型有泛型,那么返回true
                    if (genericReturnType instanceof ParameterizedType) {
                        return selectList(key, args);

                    }
                    return selectOne(key, args);
                } else {
                    return update(key, args);
                }
然后在UserMapper接口中添加如下:
   public int insert(User user) throws Exception;

    public int delete(User user) throws Exception;

    public int update(User user) throws Exception;
现在我们再来进行测试(记得测试时,都有进行安装哦):
package com;

import com.Res.Resources;
import com.dao.UserMapper;
import com.sqlsession.SqlSession;
import com.sqlsession.SqlSessionFactory;
import com.sqlsession.SqlSessionFactoryBuilder;

import java.io.InputStream;

/**
 *
 */
public class b {
    public static void main(String[] args) throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        //读取配置信息,并通过工厂模式返回一个可以操作的类
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        //每个可以操作的类也有方法,且也是工厂模式来返回操作的类
        SqlSession sqlSession = build.openSession();
        User user = new User();
        user.setId(3);
        user.setUsername("mm");
        UserMapper insert = sqlSession.getMapper(UserMapper.class);
        int insert1 = insert.insert(user);
        System.out.println(insert1);
        int update = insert.update(user);
        System.out.println(update);
        int delete = insert.delete(user);
        System.out.println(delete);

    }
}

若操作成功,那么我们的getMapper的模式就操作完毕,至此,到这里你应该可以知道mybatis的大致底层原理了吧,并且但凡操作配置文件的,一般都与反射有关系,所以我们会说,框架基本都是反射来完成的
当然,上面并不代表mybatis是上面的逻辑,因为他存在很多可能,上面只是一个实现方式而已,甚至,他的代理是直接操作类似于simpleExcutor.query的,而不是对应的方法(因为他的直接方法中,对应参数不是可变参数)
Mybatis相关概念(了解):
对象/关系数据库映射(ORM)
ORM全称Object/Relation Mapping:表示对象-关系映射的缩写
ORM完成面向对象的编程语言到关系数据库的映射,当ORM框架完成映射后,程序员既可以利用面向 对象程序设计语言的简单易用性,⼜可以利用关系数据库的技术优势,ORM把关系数据库包装成面向对 象的模型,ORM框架是面向对象设计语言与关系数据库发展不同步时的中间解决方案,采用ORM框架 后,应用程序不再直接访问底层数据库,而是以面向对象的方式来操作持久化对象,而ORM框架则将这 些面向对象的操作转换成底层SQL操作
ORM框架实现的效果:把对持久化对象的保存、修改、删除 等操作,转换为对数据库的操作
Mybatis简介:
MyBatis是一款优秀的基于ORM的半自动轻量级持久层框架(轻量级一般代表所要的资源少,一般指配置文件,连接,以及类优化好处或者类多少的总体考量),它⽀持定制化SQL、存储过程以及⾼级映射,MyBatis避免了几乎所有的JDBC代码和⼿动设置参数以及获取结果集
MyBatis可以使用简单的 XML或注解来配置和映射原⽣类型、接口和Java的POJO(Plain Old Java Objects,普通老式Java对 象) 为数据库中的记录
Mybatis历史:
原是apache的一个开源项目iBatis, 2010年6⽉这个项目由apache software foundation 迁移到了google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis ,代码于2013年11⽉迁移到Github
iBATIS一词来源于"internet"和"abatis"的组合,是一个基于Java的持久层框架,iBATIS提供的持久层框 架包括SQL Maps和Data Access Objects(DAO)
Mybatis优势:
Mybatis是一个半自动化的持久层框架,对开发人员开说,核心sql还是需要自己进行优化(所以最好是半自动,即sql自己写),sql和java编 码进行分离,功能边界清晰,一个专注业务(java),一个专注数据(sql)
分析图示如下:

在这里插入图片描述

Mybatis基本应用:
其中我们已经学习过mybatis了,你可以选择到61章博客(自然包括后面连续的博客)进行复习一遍,这是为了后面说明架构或者源码时,能更好的理解,其中在学习前面的原理后,你可以很容易的复习,并理解为什么会那样的操作了,这主要是因为我们自定义的接口或者类名称与mybatis基本类似,即这样可以更加的容易理解原理
MyBatis官⽹地址:http://www.mybatis.org/mybatis-3/
在63章博客中,有些是自己认为的原理,现在我们来看看源码是怎么操作的
这里我进行补充,即在63章博客中进行补充,在那里说明一级缓存时,基本上并没有去看看源码是如何形成或者操作的,现在开始说明(不同mybatis的版本,源码可能不同,但是最终实现还是相同的):
一级缓存原理探究与源码分析:
一级缓存到底是什么?一级缓存什么时候被创建、一级缓存的工作流程是怎样的?相信你现在应该会有 这几个疑问,那么我们就来研究一下一级缓存的本质
提到一级缓存就绕不开SqlSession,所以索性我们就直接从SqlSession,看看有没有创建缓存或者与缓存有关的属性或者方法
实际上经过我的观察,发现,基本好像只有clearCache()和缓存沾点关系,那么就直接从这个方 法入⼿,分析源码时,我们要看它(操作该clearCache方法的此类)是谁,然后分析它的⽗类和⼦类分别⼜是谁,对这些关系了解了,你才会对这个类有更深的认识,分析了一圈,你可能会得到如下这个流程图

在这里插入图片描述

public interface SqlSession extends Closeable {
 //..
    void clearCache();
   //.. 
}


public class DefaultSqlSession implements SqlSession {
     private final Configuration configuration;
    private final Executor executor;
    private final boolean autoCommit;
 //..
    public void clearCache() {
        this.executor.clearLocalCache();
    }
    //..
    
}

public interface Executor {
 //..
      void clearLocalCache();
    //..
}



public abstract class BaseExecutor implements Executor {
      private static final Log log = LogFactory.getLog(BaseExecutor.class);
    protected Transaction transaction;
    protected Executor wrapper;
    protected ConcurrentLinkedQueue<BaseExecutor.DeferredLoad> deferredLoads;
    protected PerpetualCache localCache;  //这个
    protected PerpetualCache localOutputParameterCache;
 //..
    public void clearLocalCache() {
        if (!this.closed) { //是否关闭了对应的对象,比如执行器,一般关闭会有时候抛出异常,如对应的其他地方(query,一般是子类)
            this.localCache.clear();
            this.localOutputParameterCache.clear();  //这个并非一定是二级缓存,后面会有源码解析的
            
            //注意:SqlSession的clearCache方法一般只能清空一级缓存
        }

    }
    //..
    
}

public class PerpetualCache implements Cache {
     private final String id;
    private final Map<Object, Object> cache = new HashMap();

 //..
        public void clear() {
        this.cache.clear();
    }
    //..
    
}

//从上面可以看到,他最终是执行了map的clear方法,也就验证了,一级缓存是使用map来保存的,其中key可能是多个结果的总和,而形成的key,value就是缓存信息
从上面分析可知:缓存其实就是本地存放的一个map对象,每一个SqISession都会存放一个map对象的引用,那么这个cache是何 时创 建的呢?
最有可能创建缓存的地方是哪⾥呢?一般是Executor,为什么这么认为?因为Executor是执行器,用来执行SQL请求的,而且清除缓存的方法也在Executor(接口,作为父的)中执行,所以缓存的创建一般在Executor中,看了一圈发现Executor中有一个createCacheKey方法,这个方法一般就是创 建缓存的 方法,跟进去看看,你会发现createCacheKey方法是由BaseExecutor执行的,代码如下:
public abstract class BaseExecutor implements Executor {
    //..

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            CacheKey cacheKey = new CacheKey();
            //MappedStatement 的 id
		   // id就是Sql语句的所在位置的包名+类名+ SQL方法名称,简单来说就是namespace + "."  + id
            cacheKey.update(ms.getId());
            // offset 就是 0
            cacheKey.update(rowBounds.getOffset());
            // limit 就是 Integer.MAXVALUE
            //offset和limit一般代表分页参数,当然,在没有设置时,可能日志不会进行显示
            cacheKey.update(rowBounds.getLimit());
            //具体的SQL语句
            cacheKey.update(boundSql.getSql());
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
            Iterator var8 = parameterMappings.iterator();

            while(var8.hasNext()) {
                ParameterMapping parameterMapping = (ParameterMapping)var8.next();
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    String propertyName = parameterMapping.getProperty();
                    Object value;
                    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 = this.configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }

                    //后面是update 了 sql中带的参数
                    cacheKey.update(value);
                }
            }

            if (this.configuration.getEnvironment() != null) {
                cacheKey.update(this.configuration.getEnvironment().getId());
            }

            return cacheKey;
        }
    }
    
    //..
    
}


public class CacheKey implements Cloneable, Serializable {
 //..
     private List<Object> updateList;
    //..
       public void update(Object object) {
        int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
        ++this.count;
        this.checksum += (long)baseHashCode;
        baseHashCode *= this.count;
        this.hashcode = this.multiplier * this.hashcode + baseHashCode;
        this.updateList.add(object);
    }
    //..
    
}
创建缓存key会经过一系列的update方法,update方法由一个CacheKey这个对象来执行的,这个 update方法最终由updateList的list来把五个值存进去,对照上面的代码和下面的图示,你应该能 理解 这五个值都是什么了:

在这里插入图片描述

很明显,就是包含了一系列的信息,那么key应该就是这些信息
这⾥需要注意一下最后一个值,configuration.getEnvironment().getId()这是什么,这其实就是 定义在mybatis-config.xml中的标签,⻅如下:
<environments default="development">
 <environment id="development">
 <transactionManager type="JDBC"/>
 <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>
我想应该是environments 的内容是否为null,然后来决定是否放入对应的id(id=“development”),即操作的环境
那么创建完缓存之后该用在何处呢?总不会凭空创建一个缓存不使用吧?绝对不会 的,经过对一级缓存的探究之后,我们发现一级缓存更多是用于查询操作,毕竟一级缓存也叫做查 询缓存吧,为什么叫查询缓存等下说明,我们先来看一下这个缓存到底用在哪了,我们跟踪到 BaseExecutor类的 query方法(他是执行器,自然他操作代码),看如下:
public abstract class BaseExecutor implements Executor {
    //..

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
    //创建缓存,一般只是代表key,来获取真的结果的value
        CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
        return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql); //到下面
    }
    //到这里
     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 (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache();
            }

            List list;
            try {
                ++this.queryStack;
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    //这个主要是处理存储过程用的
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                --this.queryStack;
            }

            if (this.queryStack == 0) {
                Iterator var8 = this.deferredLoads.iterator();

                while(var8.hasNext()) {
                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
                    deferredLoad.load();
                }

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    this.clearLocalCache();
                }
            }

            return list;
        }
    }

    
    public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        return this.doQueryCursor(ms, parameter, rowBounds, boundSql);
    }
    //..
    //这里就是主要的
     private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
         //protected PerpetualCache localCache;
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
         /*
           public void putObject(Object key, Object value) {
        this.cache.put(key, value); 也就是说,将CacheKey key缓存,即前面的5个信息,当成一个key放入,只与对应的ExecutionPlaceholder.EXECUTION_PLACEHOLDER我想在应该是默认的,我们继续看后面
    }
         */

        List list;
        try {
            // protected abstract <E> List<E> doQuery(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, BoundSql var5) throws SQLException;
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            this.localCache.removeObject(key);
        }

        this.localCache.putObject(key, list); //这里就保存了对应的信息,并且是覆盖的
        if (ms.getStatementType() == StatementType.CALLABLE) {
            this.localOutputParameterCache.putObject(key, parameter);
        }

        return list;
    }
    //..
  
}


//上面最终操作了 list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);,他就是执行sql语句的最终操作,并且将执行的结果进行返回,然后放入map中进行操作,当再次执行时,可以认为:
/*
 list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
                
                这里就是利用缓存的地方,即 (List)this.localCache.getObject(key),直接返回了,而不执行list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);,当然,某些操作可能会进行清空,即map的清空,所以执行,和清空的确是存在的
                
                至此我们说明的一级缓存说明完毕,简单来说,就是利用map来进行保存的,而key就是利用当前的主要信息来进行保存,即来确定唯一
*/
上面在queryFromDatabase中,会对localcache进行写入,localcache对象的put方法(简称)最终交给Map进行存放
至此,一级缓存底层的原理说明完毕,就是map集合的增加(也可以是修改),获取,删除等等操作
那么二级缓存呢,实际上你可以看成是更高等级的map缓存,这里可以这样认为,即还是一个map集合,即原理与一级缓存基本一样,前面的localOutputParameterCache一般并不是二级缓存的操作,但二级缓存的确是与一级缓存是一样的map操作(一般需要先开启,并且由于与一级缓存设置不同,所以是可以跨SqlSession的,至于他如何操作的,可能是resultHandler的原因(一般不是,以后面源码为主),具体可以百度,自己可以选择看看大致的源码,但只要知道他也是map即可,只是更加的先进行操作而已,我们只需要知道原理,可以选择死磕他的赋值细节,若只是了解,那么是没有必要的(就如10+10=20,你还要找资源来确定10+10=20一样),因为若了解,就算找到了又如何呢,只是确认一下而已,既然都确认一下了,那还不如将所有的赋值都确认吧,或者,直接阅读所有的源码吧,手动滑稽🤭,但是也最好不要,因为由于框架很多,所有基本终其一生也是看不完的,即我们只需要知道原理即可)
现在为了说明为什么缓存是查询的,我们看看这个代码:
public class BatchExecutor extends BaseExecutor {
 //..
    //也就是前面的protected abstract <E> List<E> doQuery(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, BoundSql var5) throws SQLException;
    public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var10;
        try {
            this.flushStatements();
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
            Connection connection = this.getConnection(ms.getStatementLog());
            stmt = handler.prepare(connection, this.transaction.getTimeout());
            handler.parameterize(stmt);
            var10 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var10;
    }
    //..
    
}

//我们可以发现,他的返回值并不是int类型的,也就是说,他默认是操作查询的结果,所以我大胆的认为,只有查询的操作才会进行缓存的创建,否则没有,我们可以观察他的其他操作,如类似的update里面的protected abstract int doUpdate(MappedStatement var1, Object var2) throws SQLException;,他对应就没有操作缓存(增删改,也是BatchExecutor),并且可以发现,执行改方法之前,update,会默认清空缓存,还有commit也是,虽然在63章博客有具体说明,都是操作了this.clearLocalCache();来清空的(可能包括二级,虽然有些是先清空,然后再次的给二级,但一般没有这样的考虑)
//其中增删改清空缓存,是保证我们查询的数据是对的,而不是错误的,因为我改变了,你若再从缓存拿取,那么你就没有拿取我改变的表数据了,还是原来的数据了
我们从代码层面来看,他的确没有操作缓存,但是为什么我们不给增删改操作缓存呢,我认为有以下原因:
第一:缓存是服务于多次被操作的代码,由于查询操作是非常多的,因为任何数据都是查询出来的,比如说:你在拼多多上,出现的数据,他们都是查询出来的,而像删除,增加,修改等等操作,是较少的操作,所以缓存主要给查询来分担
第二:如果他们(增删改)都操作了缓存,那么缓存并不能保证,可以更好的给出,因为在一个服务器上,如果数据够多,那么响应也会相对慢一些,对查询这样多次的操作也就会有所影响,即响应慢些了
第三:增删改是少部分的操作,那么会导致缓存会冗余(即一直保留,占用空间)
第四:虽然缓存决定对应关系,但是如果在增删改上面,若出现了没有对应(是有可能的),使得你可能是像进行减少,但是缓存操作了增加,这是非常不好的情况,而查询,无论缓存是否正确,都不会对表进行改变,所以增删改比较不安全
综上所述:所以查询操作缓存,增删改不操作缓存,注意:其实二级缓存也会清空,但还有余地,在后面会说明,所以一级缓存和二级缓存都符合上面的四点,当然,可能还有其他的说明,这里可以选择百度查看
这里还进行补充一个注解:@Options(useCache = false),即默认不操作二级缓存,作用与接口上,也就是配置文件设置useCache=“false”
在63章博客有提到过三级缓存,现在这里进行说明:
二级缓存整合redis:
mybatis有自带的二级缓存,但是这个缓存是单服务器工作,⽆法实现分布式缓存,因为他是map集合,不能被多人进行操作,自然实现不了分布式多人操作他的情况,那么 什么是分布式缓存呢?假设现在有两个服务器1和2,用户访问的时候访问了 1服务器,查询后的缓 存就 会放在1服务器上,假设现在有个用户访问的是2服务器,那么他在2服务器上就⽆法获取刚刚那个缓存,如下图所示:

在这里插入图片描述

为了解决这个问题,就得找一个分布式的缓存,专⻔用来存储缓存数据的,这样不同的服务器要缓存数 据都往它那⾥存,取缓存数据也从它那⾥取,并且他要满足可以被多人进行操作的情况,如下图所示:

在这里插入图片描述

如上图所示,在几个不同的服务器之间,我们使用第三方缓存框架,将缓存都放在这个第三方框架中,然后⽆论有多少台服务器,我们都能从缓存中获取数据,我们一般将该缓存称为mybatis的三级缓存,也就是自定义缓存
这⾥我们介绍mybatis与redis的整合:
在前面我们知道,public class PerpetualCache implements Cache {
我们看如图:

在这里插入图片描述

mybatis提供了一个Cache接口,很明显,之前的一级缓存和二级缓存都操作了他们的实现类PerpetualCache ,因为:
public abstract class BaseExecutor implements Executor {
    private static final Log log = LogFactory.getLog(BaseExecutor.class);
    protected Transaction transaction;
    protected Executor wrapper;
    protected ConcurrentLinkedQueue<BaseExecutor.DeferredLoad> deferredLoads;
    protected PerpetualCache localCache; //一级
    protected PerpetualCache localOutputParameterCache; //二级(第二个map)
    protected Configuration configuration;
所以如果要实现自己的缓存逻辑,实现cache接口(Cache的简称)并开发即可
mybatis本身默认实现了一个,但是这个缓存的实现⽆法实现分布式缓存,也就是上面的PerpetualCache类,所以我们要自己来实现
其中我们使用redis分布式缓存就可以,mybatis提供了一个针对或者说实现了cache接口的redis实现类,该类存在mybatis-redis包中:
实现(这里使用63章博客中操作的案例,可以自己进行选择,前提是学习过了,当然,自己实现或者准备基础代码也行):
pom文件:
<dependency>
 <groupId>org.mybatis.caches</groupId>
 <artifactId>mybatis-redis</artifactId>
 <version>1.0.0-beta2</version>
</dependency>

在这里插入图片描述

配置文件:
Mapper.xml:
<!--在对应的配置中操作如下:-->
<cache type="org.mybatis.caches.redis.RedisCache" />
<!--相当于使用对应的实现类来操作缓存,默认情况下是PerpetualCache类-->

<!--即默认情况下,在注解上就是这样设置的:@CacheNamespace(implementation = PerpetualCache.class),这是默认的,所以可以不写implementation
这里redis实现就是@CacheNamespace(implementation = RedisCache.class)
注解记得最好要全注解的方式,在63章博客最后面有对应的原因说明
-->

<!--那么可以说,实际上三级缓存就是来替换了二级缓存的缓存(默认的,即会设置第二个map),即mybatis本就没有三级缓存的,所以就有两种说法:
第一:我们认为自定义是另外的缓存,所以说mybatis有三级缓存
第二:他是替换的,没有三级缓存
无论哪种说法都可以
-->
在资源文件夹下创建redis.properties:
redis.host=192.168.164.128
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0
注意:可能依赖版本不同,他可能并不会判断是否是redis.开头的,而是直接使用,从而导致出现错误(如并不识别redis.host而是直接识别host),所以若出现错误,可以修改如下,即:
host=192.168.164.128
port=6379
connectionTimeout=5000
password=
database=0
上面代表你安装了redis的地址,其中,若没有操作密码,可以直接上面这样写(可以这样写哦,复制粘贴即可)
测试:
  @Test
    public void testTwoCache() throws IOException {

        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        SqlSession sqlSession1 = sqlSessionFactory.openSession();

        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);

        // 第一次查询
        User user = userMapper1.findById(11);

        System.out.println(user);

   
sqlSession1.close(); //将redis看成传统二级缓存即可,因为他必然是与原来的二级缓存是对应的,所以这些方法也是对应的作用,反正也是key-value的存储
      


        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);

        User user2 = userMapper2.findById(11);

        System.out.println(user2);

多次进行执行,或者看看redis是否有数据,一般有了数据,说明二级缓存操作完毕
源码分析:
RedisCache和普遍实现Mybatis的缓存方案大同⼩异,⽆⾮是实现Cache接口,并使用jedis操作缓存,不过该项目在设计细节上有一些区别
public final class RedisCache implements Cache {
    private final ReadWriteLock readWriteLock = new DummyReadWriteLock();
    private String id;
    private static JedisPool pool;

    public RedisCache(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        } else {
            this.id = id;
            RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
            pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(), redisConfig.getConnectionTimeout(), redisConfig.getSoTimeout(), redisConfig.getPassword(), redisConfig.getDatabase(), redisConfig.getClientName());
        }
    }

//..
}
RedisCache在mybatis启动的时候,由MyBatis的CacheBuilder创建,创建的方式很简单,就是调用 RedisCache的带有String参数的构造方法,即RedisCache(String id)(所以在PerpetualCache中,也只有对应的一个String参数的构造方法,可以说是固定的),而在RedisCache的构造方法中, 调用了 RedisConfigurationBuilder 来创建 RedisConfig 对象,并使用 RedisConfig 来创建JedisPool
RedisConfig类继承了 JedisPoolConfig,并提供了 host,port等属性的包装,简单看一下RedisConfig的 属性:
public class RedisConfig extends JedisPoolConfig {
    private String host = "localhost";
    private int port = 6379;
    private int connectionTimeout = 2000;
    private int soTimeout = 2000;
    private String password;
    private int database = 0;
    private String clientName;

    public RedisConfig() {
    }
    
    }
RedisConfig对象是由RedisConfigurationBuilder创建的,简单看下这个类的主要方法:
//   RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
final class RedisConfigurationBuilder {
        private static final RedisConfigurationBuilder INSTANCE = new RedisConfigurationBuilder();
    private static final String SYSTEM_PROPERTY_REDIS_PROPERTIES_FILENAME = "redis.properties.filename";
    private static final String REDIS_RESOURCE = "redis.properties";
    private final String redisPropertiesFilename = System.getProperty("redis.properties.filename", "redis.properties");

    //..
      public static RedisConfigurationBuilder getInstance() {
        return INSTANCE;
    }
      public RedisConfig parseConfiguration() {
        return this.parseConfiguration(this.getClass().getClassLoader());
    }
    
    //最终到这里
     public RedisConfig parseConfiguration(ClassLoader classLoader) {
        Properties config = new Properties();
         //private final String redisPropertiesFilename = System.getProperty("redis.properties.filename", "redis.properties");,这就是为什么我们需要在资源文件夹中创建redis.properties的原因,System.getProperty方法的两个参数分别代表了要获取的属性的名称和默认值,由于没有名称(或者说并没有进行设置位置),那么这里会获取默认值redis.properties,从而使得classLoader.getResourceAsStream读取了该文件里面的信息,也正好是资源文件夹下面的该文件

        InputStream input = classLoader.getResourceAsStream(this.redisPropertiesFilename);
        if (input != null) {
            try {
                //使用Properties对象的 load方法 从字节流中读取配置信息,变成map集合
                config.load(input);
            } catch (IOException var12) {
                throw new RuntimeException("An error occurred while reading classpath property '" + this.redisPropertiesFilename + "', see nested exceptions", var12);
            } finally {
                try {
                    input.close();
                } catch (IOException var11) {
                }

            }
        }

        RedisConfig jedisConfig = new RedisConfig();
        this.setConfigProperties(config, jedisConfig); //后面代码比较长,就不给出,但是很明显,他必然是读取配置文件进行设置信息,必然修改了: private String host = "localhost";,private int port = 6379;等等
        return jedisConfig;
    }
    //..
    
}

//至此,前面的 RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();就得到了具体要连接redis的信息,接下来是:
/*
 pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(), redisConfig.getConnectionTimeout(), redisConfig.getSoTimeout(), redisConfig.getPassword(), redisConfig.getDatabase(), redisConfig.getClientName());
*/

//他直接给出连接信息,返回private static JedisPool pool;,他可以这样操作:

/*
 //得到连接池的连接
        Jedis jedis = jedisPool.getResource();
        jedis.set("haha","23");
        System.out.println("添加成功");
        即可以操作redis了
*/
上面的核心的方法就是parseConfiguration方法,该方法从classpath中读取一个redis.properties文件,并将该配置文件中的内容设置到RedisConfig对象中,并返回,接下来,就是RedisCache使用 RedisConfig类创建完成JedisPool,在RedisCache中实现了一个简单的模板方法,用来操作Redis
即一般RedisCache里面存在这个方法:
private Object execute(RedisCallback callback) {
        Jedis jedis = pool.getResource();

        Object var3;
        try {
            var3 = callback.doWithRedis(jedis);
        } finally {
            jedis.close();
        }

        return var3;
    }

public interface RedisCallback {
    Object doWithRedis(Jedis var1);
}
模板接口为RedisCallback,这个接口中就只需要实现了一个doWithRedis方法而已
接下来看看Cache中最重要的两个方法:putObject和getObject,通过这两个方法来查看mybatis-redis 储存数据的格式:
  public void putObject(final Object key, final Object value) {
        this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                jedis.hset(RedisCache.this.id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));
                return null;
            }
        });
    }

    public Object getObject(final Object key) {
        return this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                return SerializeUtil.unserialize(jedis.hget(RedisCache.this.id.toString().getBytes(), key.toString().getBytes()));
            }
        });
    }

//很明显,他们都是操作execute方法,并操作hset和hget,即key value(key-value)
可以很清楚的看到,mybatis-redis在存储数据的时候,是使用的hash结构,把cache的id作为这个hash 的key (cache的id在mybatis中就是mapper的namespace),这个mapper中的查询缓存数据作为 hash的value(key-value结构,key是他传递的结果,具体多少这里不做说明),需要缓存的内容直接使用SerializeUtil存储,SerializeUtil和其他的序列化类差不多,负责 对象 的序列化和反序列化,并且,要明白,id是固定的,因为是包名加上接口名称,那么key一般就是对应配置文件的id了,很明显,他的这个结果,正好符合二级缓存的结果,即操作一个配置文件,所以put和get,就是缓存存放和获取缓存数据的操作
Mybatis插件介绍:
在62章博客中,只是大致说明了或者利用了插件,现在我们来深入了解
Mybatis作为一个应用⼴泛的优秀的ORM开源框架,这个框架具有强大的灵活性,在四大组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易用的插件扩 展机制,Mybatis对持久层的操作就是借助于实现了他们的四大核心对象
MyBatis⽀持用插件对四大核心对象的方法进行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四大对象都是被代理对象,即被增强的对象,如果包括插件的整体说明,那么他就是代理对象,只是分是否代理而已,即四大组件的实现对象是mybatis能操作sql的执行对象,而插件是增强的操作,简单来说,我们之前能够操作sql,就是利用了这个四大组件的对象来操作的

在这里插入图片描述

public interface Executor {
    //..
}

public interface StatementHandler {
 //..   
}

public interface ParameterHandler {
 //..   
}

public interface ResultSetHandler {
 //..   
}
MyBatis所允许拦截的方法如下(给出部分):
执行器Executor (update、query、commit、rollback等方法)
SQL语法构建器StatementHandler(prepare、parameterize、batch、updates,query等方法)
参数处理器ParameterHandler(getParameterObject、setParameters方法)
结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法)
Mybatis插件原理:
在四大对象创建的时候:
1:每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler)进行处理后再返回
2:获取到所有的Interceptor (拦截器)(插件需要实现的接口),调用 interceptor.plugin(target),返回 target 包装后的对象
3:插件机制,我们可以使用插件为目标对象创建一个代理对象,AOP (面向切面)使得我们的插件可 以 为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行
注意:这里直接的看说明,你自然不明白为什么,我们看后面:
拦截:
插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler来说
public interface ParameterHandler {
    Object getParameterObject();

    void setParameters(PreparedStatement var1) throws SQLException;
}

//一般只有该一个子类
public class DefaultParameterHandler implements ParameterHandler {
    
    
}

public class Configuration {
    //..
      protected final InterceptorChain interceptorChain;
    
    
    //..
     public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
         
         //操作了interceptorChain.pluginAll(parameterHandler)
        parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
    }
    
    //..
    
}


public class InterceptorChain {
 //..
    //主要操作:
     public Object pluginAll(Object target) {
        Interceptor interceptor;
        for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) { //操作拦截,或者说代理了一下,或者增强了一下,有多少,那么增强多少,注意,他只是准备,只有在执行时才会出现(这里格外要注意,在后面会再次的说明,一般都会操作增强方法的)
            interceptor = (Interceptor)var2.next(); //得到拦截
        }

        return target; //如果没有增强,自然直接的返回了
    }
    //..
    
}
//上面操作了plugin方法 
interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的,调用拦截器链中的拦截器依次的对目标进行拦截或增强,interceptor.plugin(target)中的target就可以理解为mybatis 中的四大对象,返回的target是被重重代理后的对象
如果我们想要拦截Executor的query方法,那么可以这样定义插件:
@Intercepts({
 @Signature(
 type = Executor.class,
 method = "query",
 args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
 )
}) 
public class ExeunplePlugin implements Interceptor {
 //省略逻辑

}
除此之外,我们还需将插件配置到sqlMapConfig.xml中:
<plugins>

 <plugin interceptor="com.lagou.plugin.ExamplePlugin"></plugin>
</plugins>
这样MyBatis在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链)中(pluginAll中有插件就操作增强执行),待准备工作做完后,MyBatis处于就绪状态,我们在执行SQL时,需要先通过DefaultSqlSessionFactory 创建 SqlSession,Executor实例会在创建 SqlSession 的过程中被创建, Executor实例创建完毕后,MyBatis可以通过JDK动态代理为实例⽣成代理类,这样,插件逻辑即可在 Executor相关方法被调用前执行(因为增强的,一般再创建完毕过程中就操作增强了(只是准备)),以上就是MyBatis插件机制的基本原理
自定义插件:
前面的说明,都是为这里的操作进行铺垫,所以现在我们开始进行主要操作(一般分页插件,通常存在注解的形式,所以看起来会认为只需要操作配置文件即可,依赖里面可以自己找,在62章博客就是这样)
插件接口:
Mybatis 插件接口:Interceptor
1:Intercept方法,插件的核心方法
2:plugin方法,⽣成target的代理对象
3:setProperties方法,传递插件所需参数
自定义插件过程:
设计实现一个自定义插件:
package com.lagou.test;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Connection;
import java.util.Properties;

/**
 *
 */
@Intercepts({ //注意看这个大花括号,也就这说这⾥可以定义多个@Signature对多个地方拦截,都用这个拦截器
        @Signature(
                type = StatementHandler.class, //这是指拦截哪个接口,即对象的操作
                method = "prepare",//对应接口内的那个方法名,不要拼错了
                args = {Connection.class, Integer.class}), //这是拦截的方法的入参,按顺序写到这,不要多也不要少,如果方法重载,可是要通过方法名和入参来确定唯一的
})
public class MyPlugin implements Interceptor {
    //这⾥是每次执行操作的时候,都会进行这个拦截器的方法内

    /*
    拦截方法:只要被拦截的目标对象的目标方法(一般指Signature对应的方法)被执行时,每次都会执行intercept方法
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("增强了"); //一般分页插件的操作,基本都是这里进行的,所以我们也可以在这里进行分页的操作,前提是拦截的对,至于如何进行改变的,这里就不做说明,可以自己去百度查看
        return invocation.proceed(); //执行原方法,这样才算是增强的
    }

    //主要是为了把这个拦截器⽣成一个代理放到拦截器链中
    //target为要拦截的对象
    @Override
    public Object plugin(Object target) {
        //包装目标对象 为目标对象创建代理对象,从而保证在执行时,会执行上面的intercept,所以前面才说明是只是准备,因为执行才会操作上面的增强
        //可以看到增强或者不增强,返回的都是原方法的值,实际上在前面就是pluginAll里面的return target;,即我们只是增强,并不改变对象
        System.out.println("将要包装的目标对象:" + target);
        return Plugin.wrap(target, this); //进行包装,也就是准备增强,使得pluginAll会有操作进行(给出上面的intercept但并没有直接执行哦)
    }

    //获取配置文件的属性
    //插件初始化的时候调用,也只调用一次,插件配置的属性从这⾥设置进来,即最先进行操作
    @Override
    public void setProperties(Properties properties) {
        System.out.println("插件配置的初始化参数:" + properties);
    }
}


//操作顺序:setProperties,plugin,intercept
//前面我们只给出了ParameterHandler,也就是说,如果一个类操作Intercepts时,指定了对应的类,如ParameterHandler,那么在pluginAll中会操作一次,如果有多个类或多个对应的@Signature注解(即多个增强),那么操作多次内部代码(因为是循环)
//注意:这里我们来说明前面的格外注意:我们其实要知道,对应的pluginAll实际上会操作四次,即对应的Executor,StatementHandler,ParameterHandler,ResultSetHandler
//我们在执行时,可以看到System.out.println("将要包装的目标对象:" + target);打印了四次,分别是:

/*
将要包装的目标对象:org.apache.ibatis.executor.CachingExecutor@27406a17
//可以出现"增强了"的打印
将要包装的目标对象:org.apache.ibatis.scripting.defaults.DefaultParameterHandler@1750fbeb
//可以出现"增强了"的打印
将要包装的目标对象:org.apache.ibatis.executor.resultset.DefaultResultSetHandler@6c284af
//可以出现"增强了"的打印
将要包装的目标对象:org.apache.ibatis.executor.statement.RoutingStatementHandler@5890e879
//可以出现"增强了"的打印
其中StatementHandler是最后的,可以发现,谁先执行pluginAll,那么intercept谁先执行
在前面虽然说是准备,但是其实是随时操作的,因为他操作的就是执行时,进行的准备,所以你可以选择不执行sql,就会发现,一般只有将要包装的目标对象:org.apache.ibatis.executor.CachingExecutor@27406a17了,即四大对象的确是操作执行的哦

所以操作顺序应该是:setProperties,(plugin,pluginAll,一般是pluginAll,plugin,看源码就知道了,因为他们是一体的),intercept,他们是连贯的,一个sql的完全执行,他们四个对象都会操作,后三者是可以操作多次的,而对应的在何处会操作,就不做说明了,可以自己查看,我只是给出大致流程,总不能所有的类都看一遍的,那么是没有必要的,因为世界上那么多框架,(基本)终其一生也是看不完的,我们只需要知道原理即可

//综上所述:四大对象,都有@Intercepts来针对拦截,并且是执行过程中的拦截,或者说sql形成过程中的拦截,分别是初始,参数,返回参数,sql形成的顺序,最终执行sql,所以有些时候,我们进行的增强可能是要确定是那个方面的哦,就如分页插件他是指向Executor的,因为sql执行之前,默认有 

// offset 就是 0
cacheKey.update(rowBounds.getOffset());
            // limit 就是 Integer.MAXVALUE
            //offset和limit一般代表分页参数,当然,在没有设置时,可能日志不会进行显示
            cacheKey.update(rowBounds.getLimit());
            
            //即limit,所以先设置
            
            当然,并非一定在Executor里面的,其他的操作对象也可以,所以大多数插件,基本上@Intercepts可以随便写,即随便拦截
            
            //一般来说,缓存也是包括四大对象的,所以也会因为执行不同,而导致是否继续包装的目标对象,但一般的将要包装的目标对象:org.apache.ibatis.executor.CachingExecutor@19e4653c通常只会加载一起,除非是不同的SqlSession,因为Executor实例会在创建 SqlSession 的过程中被创建
*/
sqlMapConfig.xml:
<plugins> <!--记得顺序,当然,也可以看报错的信息来查看顺序-->
        <plugin interceptor="com.lagou.test.MyPlugin">
            <!--配置参数-->
            <property name="name" value="Bob"/> <!--给setProperties初始化的信息的-->
        </plugin>
    </plugins>
在操作分页插件中,我们之所以只操作了上面的配置,是因为他对应的依赖的类中已经存在了@Intercepts的操作了,自己可以去看看,即分页插件,也是别人自定义的,所以所有的插件都是自定义的,只是他比较好用而已
源码分析:
上面插件只是我的理解,实际情况,还是需要源码来进行分析的,现在我们来分析为什么插件的操作没有错
执行插件逻辑:
Plugin实现了 InvocationHandler接口,因此它的invoke方法会拦截所有的方法调用,invoke方法会 对 所拦截的方法进行检测,以决定是否执行插件逻辑,该方法的逻辑如下:
public class Plugin implements InvocationHandler {
 //..
    //Proxy.newProxyInstance中可以操作了InvocationHandler参数,Plugin.wrap(target, this)里面可以看到
    //获取被拦截方法列表,⽐如:signatureMap.get(Executor.class), 可能返回 [query, update, commit]
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
            //检测方法列表是否包含被拦截的方法
            return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
            //执行插件逻辑:this.interceptor.intercept(new Invocation(this.target, method, args))
            //而逻辑中增强,并又返回,所以Plugin.wrap的确是执行了增强,即我们的plugin也是执行了增强
            //执行被拦截的方法:method.invoke(this.target, args)
        } catch (Exception var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }
    //..
    
    
}
invoke方法的代码⽐较少,逻辑不难理解,⾸先,invoke方法会检测被拦截方法是否配置在插件的 @Signature注解中,若是,则执行插件逻辑,否则执行被拦截方法(也就是没有操作增强了),插件逻辑封装在intercept中,该 方法的参数类型为Invocation,Invocation主要用于存储目标类,方法以及方法参数列表,下面简单看 一下该类的定义
public class Invocation {
    
     private final Object target;
    private final Method method;
    private final Object[] args;

    public Invocation(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
    }
    //..
     public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return this.method.invoke(this.target, this.args);
    }
    
}


//所以之前的:
 @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("增强了"); //一般分页插件的操作,基本都是这里进行的,所以我们也可以在这里进行分页的操作,前提是拦截的对
        return invocation.proceed(); //执行原方法,这样才算是增强的
    }

//上面proceed就是返回原方法
至此,关于为什么对应插件的执行操作没有错的逻辑就分析结束,也的确对应的plugin操作的是增强的(且是JDK方式的动态代理)
至于分页插件,可以选择到62章测试一下,这里不做说明了
通用 mapper:
什么是通用Mapper:
通用Mapper是主要为了解决单表增删改查的,比如说我要查询id为1,或者查询名称为1,或者他们的结合,像这样的每个字段都进行单独查询或者关联的,若是操作接口,那么需要写非常多的接口方法(字段可是有多个的),所以我们需要通用的mapper,即以类来自动的生成sql,基于Mybatis的插件机制,开发人员不需要编写SQL,不需要 在DAO中增加方法,只要写好实体类,就能⽀持相应的增删改查方法,这里可以选择参照mp,所以说mp就是mybatis的自定义的插件,这也是为什么mp是在mybatis上的只做增强,而不改变mybatis的主要原因,这里的说明也是为什么mp不好操作复杂sql的原因(因为是主要为了解决单表增删改查的),当然,既然是增强,mp自然可以通过某些配置来操作复杂的,只是非常麻烦(具体可以百度,一般我们只是手动的进行关联而已)
如何使用:
⾸先在maven项目,在pom.xml中引入mapper的依赖
<dependency>
 <groupId>tk.mybatis</groupId>
 <artifactId>mapper</artifactId>
 <version>3.1.2</version>
</dependency>
Mybatis配置文件中完成配置:
<plugins>

 <plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor"> 
 <!-- 指定通用Mapper接口,多个通用接口用逗号隔开 -->
 <property name="mappers" value="com.lagou.mapper.UserMapper"/>
 </plugin>
</plugins>
实体类设置主键:
//假设有这个类,这里主要看对应的注解的作用是什么,至于是否存在这个类,并不需要了解

//看对应那个表名
@Table(name = "t_user") //mp中就有这个javax.persistence.Table;,所以也证明了mp是利用了增强,即自定义插件
//实际上直接导入javax.persistence.Table;也行,看依赖就知道了,对应的tk.mybatis基本只有他一个(依赖到顶,说明没有其他的包导入了,但并不代表没有到顶的没有类的代码,maven只是看导入的而已,自身的还是有的,自己看包就知道了)
public class User {
 @Id //表示主键id
 @GeneratedValue(strategy = GenerationType.IDENTITY) //表示:主键的生成策略,操作返回SELECT LAST_INSERT_ID()(一般指: insert 进去记录的主键值,但是有时为1,即返回行数,具体解决可以百度),这里建议不操作这个,他可能导致原来的对象id变成0
 private Integer id;
 @Column(name = "username") //表示:对应的表字段名称,一般不写是有默认值的,具体自己测试,但只要变量名称与表字段对应,通常不会出现错误(一般也包括驼峰,他通常只是将大写前面加上_,而不会在加上_后将大写变成小写,即还是大写,与mp是不同的)
 private String username;
}
定义通用mapper:
public interface UserMapper extends Mapper<User> {

	//只是增强,配置接口和读取配置文件可以不用改变,只需要继承即可
//一般来说不能与插件的方法一致,因为对方注解的原因,不能给覆盖,所以使得写上编译会报错,这里了解即可
    //而在mp中可能是配置文件优先
}
测试:
package com.lagou.test;

import com.lagou.domain.User;
import com.lagou.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import tk.mybatis.mapper.entity.Example;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 *
 */
public class UserTest {
    @Test
    public void test1() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();

        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        User user = new User();
        user.setId(4);
        //mapper基础接口
        根据实体中的属性进行查询,只能有—个返回值
        User user1 = userMapper.selectOne(user);
        List<User> users = userMapper.select(null); //查询全部结果
        //根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号(如where后面的id=1,的这样的=的等号)
        userMapper.selectByPrimaryKey(1);
        //根据实体中的属性查询总数,查询条件使用等号
        userMapper.selectCount(user);

        //insert 接口
        //保存一个实体,null值也会保存,不会使用数据库默认值
        user.setUsername("null");
        int insert = userMapper.insert(user);
        System.out.println(insert); //1
        System.out.println(user); //id通常会变成0
        sqlSession.commit(); //没有整合什么,如spring,或者spring boot,那么没有自动提交,即我们操作提交的
        user = new User();
        user.setId(44);
//        user.setUsername("null"); ,会报错
        //保存实体,null的属性不会保存即报错,即只会使用数据库默认值
        int i = userMapper.insertSelective(user);
        sqlSession.commit();

        // update 接口
        user = new User();
        user.setId(44);
        user.setUsername("1");
        int i1 = userMapper.updateByPrimaryKey(user);//根据主键更新实体全部字段,null值会被更新
        sqlSession.commit();

        // delete 接口
        user = new User();
        user.setId(44);
        int delete = userMapper.delete(user); //根据实体属性作为条件进行删除,查询条件使用等号
        sqlSession.commit();
        userMapper.deleteByPrimaryKey(1); //根据主键字段进行删除,方法参数必须包含完整的主键属性
        sqlSession.commit();

        //example方法
        Example example = new Example(User.class);
        //手动的设置对应的值,其中id和username需要存在,否则报错
        example.createCriteria().andEqualTo("id", 1); //如果EqualTo存在,一般like不会操作,因为基本只能是一个createCriteria()操作(代表查询所有,后面的就是操作where条件,所以也可以直接的example.createCriteria();),所以如果需要那么这样即可: example.createCriteria().andLike("username", "1").andEqualTo("id", 1);,因为他们返回本身的,一般我们最好使用这个自定义来操作,比较方便(前面的操作最终都是自定义形成的,就如mp可能也是如此)
      
        example.createCriteria().andLike("username", "1");
        //自定义查询
        List<User> users1 = userMapper.selectByExample(example);
        System.out.println(users1);


    }

}

实际上会有些细节问题,而mp中就进行了解决,所以我们尽量使用mp,或者自己定义,而不是使用对应单纯的一个依赖来操作
Mybatis架构原理:
我们知道了mybatis的具体sql操作,但是他的整体架构是什么呢
架构设计:

在这里插入图片描述

我们把Mybatis的功能架构分为三层:
(1):API接口层:提供给外部使用的接口 API,开发人员通过这些本地API来操纵数据库,接口层接收 到 调用请求就会调用数据处理层来完成具体的数据处理
MyBatis和数据库的交互有两种方式:
1:使用传统的MyBatis提供的API
2:使用Mapper代理的方式(本质也是API,但解决了sql的定位硬编码)
(2):数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等,它主要的目的是根 据调用的请求完成一次数据库操作
(3):基础⽀撑层:负责最基础的功能⽀撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是 共 用的东⻄,将他们抽取出来作为最基础的组件,为上层的数据处理层提供最基础的⽀撑
主要构件及其相互关系:

在这里插入图片描述

在这里插入图片描述

总体流程:
(1):加载配置并初始化:
触发条件:加载配置文件
配置来源于两个地方,一个是配置文件(主配置文件conf.xml,mapper文件*.xml),—个是java代码中的注解,将主配置文件内容解析封装到Configuration,将sql的配置信息加载成为一个mappedstatement 对象,存储在内存之中
(2):接收调用请求:
触发条件:调用Mybatis提供的API
传入参数:为SQL的ID和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理
(3):处理操作请求
触发条件:API接口层传递请求过来
传入参数:为SQL的ID和传入参数对象
处理过程(这个过程可以认为是类似之前我们手写mybatis底层的query方法):
1:根据SQL的ID查找对应的MappedStatement对象
2:根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数
3:获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果
4:根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处 理 结果
5:可以释放连接资源
(4):返回处理结果
将最终的处理结果返回
Mybatis源码剖析:
上面只是直接的说明流程,虽然我们可以将前面的手写的简化的mybatis流程是这样的认为,但是最好的方式,还是直接的看源码,所以我们现在直接开始进行源码的剖析
传统方式源码剖析:
源码剖析的初始化:
//读取配置文件,得到字节输入流,但是还没有进行解析
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
/*
public class Resources {
 private static ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper();
  
//..
  public static InputStream getResourceAsStream(String resource) throws IOException {
        return getResourceAsStream((ClassLoader)null, resource);
    }
      public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
        InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
        if (in == null) {
            throw new IOException("Could not find resource " + resource);
        } else {
            return in;
        }
    }
    //至于后面如何操作的,这里不做说明了


//..
}
*/

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//要进行剖析,自然是看如何操作的,因为只有操作了,我们才能知道他是如何利用的,当然,如果你水平足够,可以直接的选择去依赖里查看源码,但是这并不是好的办法,所以我们选择从使用开始,然后到解析的方式来进行剖析
上面很明显,首先读取配置文件,那么后面的那一行代码正是初始化工作的开始
实际上之所以我们先手写一个类似的,主要是为了源码更加的明白来做准备,当然,既然是类似的,那么具体的流程并非一定相同(一般大体相同的),所以只需要参照手写的即可,而不要将手写的看成是主体
public class SqlSessionFactoryBuilder {
 //..
    
    //我们最初调用的build
    public SqlSessionFactory build(InputStream inputStream) {
        //调用了重载方法
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
    }
    
    //..
    //到这里来
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            // XMLConfigBuilder是专⻔解析mybatis的配置文件的类
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            //这⾥⼜调用了一个重载方法,parser.parse()的返回值是Configuration对象
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
            }

        }

        return var5;
    }
    
    //..
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config); //工厂模式,但是其实这里也有个设计模式,我们一般称为构建者设计模式,那么这个构建者设计模式是什么呢,其实在前面我们也有说明:实际上工厂模式除了将创建对象给方法外,还有一个最为重要的操作,即可以将操作分开,使得一个类不会有多个操作
        //构建者设计模式是指:将操作进行分开,而不用返回整个对象或者多余操作的对象,只需要返回具体需要用到变量的对象,就如你只要操作一个变量,我创建类来保存这个变量并返回这个类对象,而不是返回了当前或者其他有这个变量,但是还有其他变量的当前类或者其他类对象,当然不只是变量,方法也是可以包括的哦
    }
    
}



public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
    
 //..   
    
}
MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使用 org.apache.ibatis.session.Configuration 实例来维护
下面进入对配置文件解析部分:
⾸先对Configuration对象进行介绍:
/*
Configuration对象的结构和xml配置文件的对象几乎相同
回顾一下xml中的配置标签有哪些:
properties (属性)
settings (设置)
typeAliases (类型别名)
typeHandlers (类型处理器)
objectFactory (对象工厂)
mappers (映射器)
上面的等等,Configuration也有对应的对象属性来封装它们
也就是说,初始化配置文件信息的本质就是创建Configuration对象,将解析的xml数据封装到Configuration内部属性中

这里我们参照之前的手写的代码(自定义的mybatis框架):
*/
 XMLConfigerBuilder xmlConfigerBuilder = new XMLConfigerBuilder(configuration); //该configuration是创建的
Configuration configuration = xmlConfigerBuilder.parseConfiguration(inputStream);
 SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);

//可以发现,最终的xmlConfigerBuilder.parseConfiguration(inputStream);就类似于上面源码中的parser.parse()
//所以现在我们来看看parser.parse()
public class XMLConfigBuilder extends BaseBuilder {
    
    //..
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    }
     private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration()); //创建新的new Configuration(),利用自己的父类哦
        this.localReflectorFactory = new DefaultReflectorFactory();
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }
 //..
    //解析 XML 成 Configuration 对象
    public Configuration parse() {
        //若已解析,抛出BuilderException异常,当然,由于 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);即是新创建的,所以这里基本不会出现抛出异常
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            //标记已解析
            this.parsed = true; //设置为true
            // 解析 XML configuration 节点,也就是读取对应的信息
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration; //返回创建的该类
        }
    }
    
    //解析XML
     private void parseConfiguration(XNode root) {
        try {
            // 解析 <properties /> 标签
            this.propertiesElement(root.evalNode("properties"));
            // 解析<settings /> 标签
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            //加载自定义的VFS实现类和Log的实现类(一般是mybatis的哦)
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            //解析 <typeAliases /> 标签
            this.typeAliasesElement(root.evalNode("typeAliases"));
            //解析<plugins />标签
            this.pluginElement(root.evalNode("plugins"));
            // 解析 <objectFactory /> 标签
            this.objectFactoryElement(root.evalNode("objectFactory"));
            // 解析 <objectWrapperFactory /> 标签
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析 <reflectorFactory /> 标签
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 赋值 <settings /> ⾄ Configuration 属性
            this.settingsElement(settings); //这里操作了很多,特别是后面的ExecutorType的获取,他操作了setDefaultExecutorType,至于得到了什么,自己可以选择去看,但只要了解即可,可以不用去看,从而在后面的说明中可以通过getDefaultExecutorType来得到
            // 解析<environments /> 标签 
            this.environmentsElement(root.evalNode("environments"));
            // 解析 <databaseIdProvider /> 标签
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 解析 <typeHandlers /> 标签
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            //解析<mappers />标签
            this.mapperElement(root.evalNode("mappers")); //会进一步解析的
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }
    //..
    
}
至于解析后的操作,就不用说明了,因为非常麻烦,我们知道这里解析了即可,简单来说的确操作了类似于我们手写的:
XMLConfigerBuilder xmlConfigerBuilder = new XMLConfigerBuilder(configuration); //该configuration是创建的
Configuration configuration = xmlConfigerBuilder.parseConfiguration(inputStream);
 SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);

//并且对应也返回了DefaultSqlSessionFactory,他用来生成SqlSession,一般有很多重载的openSession方法
前面之所以要手写除了类似外,也是为了更好的理解,那么从这里开始,自己选择去找对应的,就不操作上面的直接说明了,当然,可以的话,我会顺便给出代码的
介绍一下 MappedStatement :
作用:MappedStatement与Mapper配置文件中的一个select/update/insert/delete节点相对应
mapper中配置的标签都被封装到了此对象中,主要用途是描述一条SQL语句
初始化过程:回顾刚开 始介绍的加载配置文件的过程中,会对sqlMapConfig.xml中的各个标签都进行 解析,其中有mappers 标签用来引入mapper.xml文件或者配置mapper接口的目录,如:
<select id="getUser" resultType="user" > 
 select * from user where id=#{id} 

</select>
这样的一个select标签会在初始化配置文件时被解析封装成一个MappedStatement对象,然后存储在 Configuration对象的mappedStatements属性中,mappedStatements 是一个HashMap,存储时key =全限定类名+方法名,value =对应的MappedStatement对象,正好与我们手写的是有对应的
在configuration中对应的属性为:
public class Configuration {
 //..
        protected final Map<String, MappedStatement> mappedStatements;
    
    //..
    
}

//而前面 this.mapperElement(root.evalNode("mappers"));就操作了这些
源码剖析的执行SQL流程:
先简单介绍SqlSession :
SqlSession是一个接口,它有两个实现类:DefaultSqlSession (默认)和SqlSessionManager (弃用,不做介绍)
public interface SqlSession extends Closeable {
    //..
    
    //..
}


public class DefaultSqlSession implements SqlSession {
        private final Configuration configuration;
    private final Executor executor;
    //..
    //最终是这里
      public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = executor;
        this.dirty = false;
        this.autoCommit = autoCommit;
    }

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

    //..
}

public class SqlSessionManager implements SqlSessionFactory, SqlSession {
    //..
    
    //..
    
}
SqlSession是MyBatis中用于和数据库交互的顶层类,通常将它与ThreadLocal绑定,一个会话使用一 个SqlSession,并且可以在使用完毕后需要close,具体的ThreadLocal使用可以参照65章博客,简单来说,一个线程有独有自己的一份数据
SqlSession中(实现类DefaultSqlSession)的两个最重要的参数,configuration是赋值的,Executor为执行器
Executor:
Executor也是一个接口,他有三个常用的实现类:
BatchExecutor (重用语句并执行批量更新)
ReuseExecutor (重用预处理语句 prepared statements)
SimpleExecutor (普通的执行器,默认)
public interface Executor {
    
    
}

public class BatchExecutor extends BaseExecutor {
    
}
public class ReuseExecutor extends BaseExecutor {
    
}
public class SimpleExecutor extends BaseExecutor {
    
}

public abstract class BaseExecutor implements Executor {
}


继续分析,初始化完毕后,我们就要执行SQL 了(假设是如下):
   InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);  
//从这里开始了
SqlSession sqlSession = build.openSession();
        List<User> objects = sqlSession.selectList("userMapper.findAll");
获得 sqlSession:
//之前返回的是:return new DefaultSqlSessionFactory(config);
public class Configuration {
    //..
    protected ExecutorType defaultExecutorType;
    //..
     public ExecutorType getDefaultExecutorType() {
        return this.defaultExecutorType;
    }
     public void setDefaultExecutorType(ExecutorType defaultExecutorType) {
        this.defaultExecutorType = defaultExecutorType;
    }
    //..
}
public interface SqlSessionFactory {

    //..
    
    //..
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
       private final Configuration configuration;

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

    //..
     public SqlSession openSession() {
         //getDefaultExecutorType()得到的是ExecutorType,一般是SIMPLE
         /*
         this.settingsElement(settings); //这里操作了很多,特别是后面的ExecutorType的获取,他操作了setDefaultExecutorType,从而在后面的说明中可以通过getDefaultExecutorType来得到
         */
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }
    //..
     private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
         //进入openSessionFromDataSource
         //TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务自动提交(增删改是默认开启事务的),这里就是false,那么不自动提交,这就是为什么增删改,需要我们自动提交的原因
         
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            //根据参数创建指定类型的Executor
            Executor executor = this.configuration.newExecutor(tx, execType);
            //返回的是 DefaultSqlSession
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }
    //..
}

执行 sqlsession 中的 api:
public class DefaultSqlSession implements SqlSession {
    private final Configuration configuration;
    private final Executor executor;
    private final boolean autoCommit;
    private boolean dirty;
    private List<Cursor<?>> cursorList;

    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = executor;
        this.dirty = false;
        this.autoCommit = autoCommit;
    }

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

    //..
    //List<User> objects = sqlSession.selectList("userMapper.findAll");
      public <E> List<E> selectList(String statement) {
        return this.selectList(statement, (Object)null);
    }

    public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
            //根据传入的全限定名+方法名从映射的Map中取出MappedStatement对象
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            //调用Executor中的方法处理,private final Executor executor;
            //RowBounds是用来逻辑分⻚
            //wrapCollection(parameter)是用来装饰集合或者数组参数
            //Executor接口的<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); //他中间进行了一系列的操作,而不是我们自定义框架的一步到位的,这里我只是以查询为例子,具体的增删改等等操作,可以自己去看看,但大体是类似的
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }

        return var5;
    }
    //..
}
上面可以看成我们自定义框架中的DefaultSqlSession的selectList方法,虽然Executor是当成参数传递,而不是在里面当成成员变量
源码剖析的executor:
继续源码中的步骤,进入executor.query()
//一般是BaseExecutor(是SimpleExecutor的父类,他没有query,所以要执行BaseExecutor的query),最终操作的是SIMPLE,得到了executor = new SimpleExecutor(this, transaction);
//Executor executor = this.configuration.newExecutor(tx, execType);,execType最终操作的是SIMPLE
public abstract class BaseExecutor implements Executor {
    
    //..
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
        BoundSql boundSql = ms.getBoundSql(parameter);
        //为本次查询创建缓存的Key
        CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
        return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    
     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 (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache();
            }

            List list;
            try {
                ++this.queryStack;
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    //如果缓存中没有本次查找的值,那么从数据库中查询
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                --this.queryStack;
            }

            if (this.queryStack == 0) {
                Iterator var8 = this.deferredLoads.iterator();

                while(var8.hasNext()) {
                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
                    deferredLoad.load();
                }

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    this.clearLocalCache();
                }
            }

            return list;
        }
    }

    //..
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

        List list;
        try {
            //查询的方法,到SimpleExecutor里面去执行
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            this.localCache.removeObject(key);
        }

        //将查询结果放入缓存
        this.localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            this.localOutputParameterCache.putObject(key, parameter);
        }

        return list;
    }

    
    //..
}

public class SimpleExecutor extends BaseExecutor {
    //..
 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            //传入参数创建StatementHanlder对象来执行查询
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //创建jdbc中的statement对象
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            // StatementHandler 进行处理
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }
    //..
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        //这里的代码中的getConnection方法经过重重调用最后会调用openConnection方法,从连接池中获得连接
        //JdbcTransaction里面的getConnection方法,最终操作的是tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        Connection connection = this.getConnection(statementLog);
        //public interface PreparedStatement extends Statement {
        Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
        //设置sql,如操作占位符
        handler.parameterize(stmt);
        return stmt;
    }
}

上述的Executor.query()方法几经转折,最后会创建一个StatementHandler对象,然后将必要的参数传递给StatementHandler,使用StatementHandler来完成对数据库的查询,最终返回List结果集
从上面的代码中我们可以看出,Executor的功能和作用是:
/*
根据传递的参数,完成SQL语句的动态解析,⽣成BoundSql对象,供StatementHandler使用
为查询创建缓存,以提⾼性能
创建JDBC的Statement连接对象,传递给StatementHandler对象,最终返回List查询结果
*/
源码剖析的StatementHandler:
StatementHandler对象主要完成两个工作:
对于JDBC的PreparedStatement类型的对象,创建的过程中,我们使用的是SQL语句字符串会包含若⼲个?占位符,我们其后再对占位符进行设值,StatementHandler通过parameterize(statement)方法对Statement 进行设值
StatementHandler 通过< E> List< E> query(Statement var1, ResultHandler var2) throws SQLException;方法来 完成执行Statement,和将Statement对象返回的resultSet封装成List
进入到 StatementHandler 的 parameterize(statement)方法的实现:
//上面的handler.parameterize(stmt);,下面先是RoutingStatementHandler,然后才是PreparedStatementHandler
//PreparedStatementHandler类的(PREPARED),至于为什么就不做说明,可以自己调试,或者自己找对应的代码
 public void parameterize(Statement statement) throws SQLException {
        this.parameterHandler.setParameters((PreparedStatement)statement);
    }

public class DefaultParameterHandler implements ParameterHandler {
 //..
    //ParameterHandler 类(一般这样说明的接口,通常代表其实现类)的 setParameters(PreparedStatement ps) 实现对某一个Statement进行设置参数
      public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
        List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for(int i = 0; i < parameterMappings.size(); ++i) {
                ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    String propertyName = parameterMapping.getProperty();
                    Object value;
                    if (this.boundSql.hasAdditionalParameter(propertyName)) {
                        value = this.boundSql.getAdditionalParameter(propertyName);
                    } else if (this.parameterObject == null) {
                        value = null;
                    } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
                        value = this.parameterObject;
                    } else {
                        MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
				// 每一个 Mapping都有一个 TypeHandler,根据 TypeHandler 来对 preparedStatement 进 行设置参数
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    //获取jdbc类型,这里就是mysql
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = this.configuration.getJdbcTypeForNull();
                    }

                    try {
                        //设置参数
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (SQLException | TypeException var10) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
                    }
                }
            }
        }

    }
 
    
}

从上述的代码可以看到,StatementHandler的parameterize(Statement)方法调用了ParameterHandler的setParameters(statement)方法
ParameterHandler的setParameters(Statement )方法负责根据我们输入的参数,对statement对象的 ?占位符处进行赋值
进入到StatementHandler 的< E> List< E> query(Statement var1, ResultHandler var2) throws SQLException;方法的 实现:
//也是PreparedStatementHandler:

 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
     //调用preparedStatemnt.execute()方法,然后将resultSet交给ResultSetHandler处理   
     PreparedStatement ps = (PreparedStatement)statement;
        ps.execute();
     //使用 ResultHandler 来处理 ResultSet
        return this.resultSetHandler.handleResultSets(ps);
    }

/*
从上述代码我们可以看出,StatementHandler 的query方法的实现
是调用了 ResultSetHandler 的handleResultSets(Statement)方法
ResultSetHandler 的 handleResultSets(Statement)方法
会将 Statement 语句执行后⽣成的 resultSet结果集转换成List结果集
//public interface PreparedStatement extends Statement {
*/


public class DefaultResultSetHandler implements ResultSetHandler {
 //..
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
        //多ResultSet的结果集合,每个ResultSet对应一个Object对象,而实际上,每个 Object 相当于是 List<Object> 对象(因为多个列的)
//在不考虑存储过程的多ResultSet的情况(因为存储过程可以执行多个查询,而不是一个,具体jdbc怎么获取存储过程的数据,可以选择百度查看)下,那么普通的查询,实际就一个ResultSet,也就是说,multipleResults最多就一个元素
        List<Object> multipleResults = new ArrayList();
        int resultSetCount = 0;
        //获得⾸个ResultSet对象,并封装成ResultSetWrapper对象
        ResultSetWrapper rsw = this.getFirstResultSet(stmt);
        //获得ResultMap数组
 //在不考虑存储过程的多ResultSet的情况,普通的查询,实际就一个ResultSet,也就是说,resultMaps就一个元素
        List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        this.validateResultMapsCount(rsw, resultMapCount); // 校验

        while(rsw != null && resultMapCount > resultSetCount) {
            //获得ResultMap对象
            ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
            //处理ResultSet,将结果添加到multipleResults中
            this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
            //获得下一个ResultSet对象,并封装成ResultSetWrapper对象
            rsw = this.getNextResultSet(stmt);
            //清理
            this.cleanUpAfterHandlingResultSet();
            ++resultSetCount;
        }
        //很明显,上面就是得到信息,最后会变成list集合的
        
//因为mappedStatement.resultSets只在存储过程中使用,本系列暂时不考虑,忽略即可
        String[] resultSets = this.mappedStatement.getResultSets();
        if (resultSets != null) {
            while(rsw != null && resultSetCount < resultSets.length) {
                ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
                if (parentMapping != null) {
                    String nestedResultMapId = parentMapping.getNestedResultMapId();
                    ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
                    this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
                }

                rsw = this.getNextResultSet(stmt);
                this.cleanUpAfterHandlingResultSet();
                ++resultSetCount;
            }
        }
//如果是multipleResults单元素,则取⾸元素返回,否则都返回,这里是考虑存储过程
        return this.collapseSingleResultList(multipleResults);
    }
    
    //..
    
    
}
因为是list集合,那么最终的selectList方法就这个这个返回了,而因为返回值类型是泛型方法的返回值,所以不用强转(自定义泛型方法一般不用强转的,只看最终的结果,但也只是以泛型为主的对应),至此传统方式大致说明完毕
Mapper代理方式:
//假设是如下:
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 List<User> list = mapper.getUserByName("tom");
思考一个问题,通常的Mapper接口我们都没有实现的方法却可以使用,是为什么呢?答案很简单,是动态代理
开始之前介绍一下MyBatis初始化时对接口的处理:MapperRegistry是Configuration中的一个属性(一般是创建自身并传递this的对象值,this.mapperRegistry = new MapperRegistry(this);,且是在Configuration的无参构造方法里面操作的)
它内部维护一个HashMap用于存放mapper接口的工厂类,每个接口对应一个工厂类,mappers中可以 配置接口的包路径,或者某个具体的接口类
<mappers>
 <!--<mapper class="com.lagou.mapper.UserMapper"/>,一般来说不能读取多个,读取多个会报错,这是代码中存在判断的,因为读取一次,就需要进行一系列的封装和覆盖,比较麻烦,浪费性能,所以大多数框架中,一般都不会操作重复的-->
 <package name="com.lagou.mapper"/>
</mappers>
当解析mappers标签时,它会判断解析到的是mapper配置文件时,会再将对应配置文件中的增删改查标签 封装成MappedStatement对象,存入mappedStatements中(Configuration里面的,一般只有一个map),当判断解析到接口时(以getMapper作为判断,因为方法不同的吗),会建此接口对应的MapperProxyFactory对象(包含多个接口里的接口方法,所以对应的MapperProxyFactory里面还有一个map来定位方法,即总共两个map,即操作了分开,即namespace和id分开了,而不是一起了),存入HashMap中(MapperRegistry里面的,也在Configuration里面),key =接口的字节码对象,value =此接 口对应的MapperProxyFactory对象,内部最终还是原版操作的
源码剖析的getmapper():
进入 sqlSession.getMapper(UserMapper.class )中:
public class MapperRegistry {
      private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

 
    //..
    //最终到这里
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        //得到对应类的所有接口方法信息
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                //通过动态代理工厂⽣成示例
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

    //..
}


public class MapperProxyFactory<T> {  
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap();

 //..
    
    //..
    //最终到这里,很明显是jdk代理,与我们自定义的mybatis框架是使用同一种代理
     protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }
    
    
     public T newInstance(SqlSession sqlSession) {
         //创建了 JDK动态代理的Handler类
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
         //调用了重载方法
        return this.newInstance(mapperProxy);
    }
}


public class MapperProxy<T> implements InvocationHandler, Serializable {
     private static final long serialVersionUID = -4724728412955527868L;
    private static final int ALLOWED_MODES = 15;
    private static final Constructor<Lookup> lookupConstructor;
    private static final Method privateLookupInMethod;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperProxy.MapperMethodInvoker> methodCache;

    //构造,传入了 SqlSession,说明不同的session中的代理对象的不同的,即最终的sql操作必然不同,因为SqlSession不同,那么DefaultSqlSession也就不同哦,那么对应的传统代码信息必然也是不同的,虽然读取的配置文件可能相同,但也保证了 var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);是不同的,即不会操作到同一个,那么缓存的一级和二级怎么对比呢,实际上他根据id的,并且有确定是否对应的配置文件,所以才使得一级和二级共用
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperProxy.MapperMethodInvoker> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }
 //..
    
    
    //..
    
}
源码剖析的invoke():
既然是jdk代理,那么必然是操作了invoke()方法来进行增强操作,使得可以通过接口来得到sql并执行(主要是可以自动定位的操作,而不是硬编码的出现),我们可以进行观察:
//MapperProxy类里面的 
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //增强是增强,我们调用对应方法返回的结果自然也是invoke的返回值哦
            
            //如果是Object定义的方法,直接调用
            return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }
上面在动态代理返回了示例后,我们就可以直接调用mapper类中的方法了,但代理对象调用方法,执行是 在MapperProxy中的invoke方法中
我们进入如下:
//this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
public class MapperProxy<T> implements InvocationHandler, Serializable {
    //..
    //操作this.cachedInvoker(method)
   private MapperProxy.MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
            return (MapperProxy.MapperMethodInvoker)this.methodCache.computeIfAbsent(method, (m) -> {
                if (m.isDefault()) {
                    try {
                        return privateLookupInMethod == null ? new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava9(method));
                    } catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) {
                        throw new RuntimeException(var4);
                    }
                } else {
                    return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
                }
            });
        } catch (RuntimeException var4) {
            Throwable cause = var4.getCause();
            throw (Throwable)(cause == null ? var4 : cause);
        }
    }
    //..
    
}

//最终到这里(调试就知道了):
//MapperProxy的内部类
    private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker {
        private final MapperMethod mapperMethod;

        public PlainMethodInvoker(MapperMethod mapperMethod) {
            this.mapperMethod = mapperMethod;
        }

        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
            //执行了这个方法
            return this.mapperMethod.execute(sqlSession, args);
        }
    }

进入execute方法:
public class MapperMethod {
 //..
    
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        //判断mapper中的方法类型,可以发现最终调用的还是SqlSession中的方法
        switch(this.command.getType()) {
        case INSERT:
                //转换参数
            param = this.method.convertArgsToSqlCommandParam(args);
                //执行INSERT操作
                // 转换 rowCount
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
                //⽆返回,并且有ResultHandler方法参数,则将查询的结果,提交给 ResultHandler 进行处理
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
                //执行查询,返回列表
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
                //执行查询,返回Map
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
                //执行查询,返回Cursor
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
                //执行查询,返回单个对象
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                //查询单条
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }
//返回结果为null,并且返回类型为基本类型,则抛出BindingException异常
        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            //返回结果
            return result;
        }
    }
    //..
    
    
}
很明显,与之前说的一样,他最终还是操作传统的方法,对应的参数并没有说明具体怎么来的,但是实际上他就是进行了操作,这是因为前面我只是大致的给出方向,并没有更加的细分,就如前面说的框架很多,终其一生也是看不完的,我们只需要知道原理即可
二级缓存源码剖析:
前面我们说明了一级缓存的操作,但是二级缓存并没有给出具体的源码实现,所以现在来进行分析
二级缓存构建在一级缓存之上,在收到查询请求时,MyBatis ⾸先会查询二级缓存,若二级缓存未命 中,再去查询一级缓存,一级缓存没有,再查询数据库
与一级缓存不同,二级缓存和具体的命名空间绑定,一个Mapper中有一个Cache,且相同Mapper中的MappedStatement共用一个Cache(该一个Cache),一级缓存则是和 SqlSession 绑定
启用二级缓存:
开启全局二级缓存配置:
<settings>
 <setting name="cacheEnabled" value="true"/>
 </settings> <!--默认为true,所以可以不用配置-->
在需要使用二级缓存的Mapper配置文件中配置< cache>标签
<cache></cache>
在具体CURD标签上配置 useCache=true:
<select id="findById" resultType="com.lagou.pojo.User" useCache="true">
 select * from user where id = #{id}
 </select>
<!--标签上的useCache是默认true的,所以也可以不用配置-->
由于其他两个配置都默认为true,所以我们主要看< cache>标签
标签 < cache/> 的解析:
根据之前的mybatis源码剖析,xml的解析工作主要交给XMLConfigBuilder.parse()方法来实现
public class XMLConfigBuilder extends BaseBuilder {
    //..

public Configuration parse() {
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            // 就是这⾥
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

//..
private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();

            while(true) {
                while(var2.hasNext()) {
                    XNode child = (XNode)var2.next();
                    String resource;
                    if ("package".equals(child.getName())) {
                        resource = child.getStringAttribute("name");
                        this.configuration.addMappers(resource);
                    } else {
                        resource = child.getStringAttribute("resource");
                        String url = child.getStringAttribute("url");
                        String mapperClass = child.getStringAttribute("class");
                        XMLMapperBuilder mapperParser;
                        InputStream inputStream;
                        // 假设按照对应的配置到这里,则直接⾛该if判断(因为走包找类的话,那么基本就是先找路径,最后找标签,而不是直接的找标签,所以不好查找,但是最终肯定是类似的,所以我们直接看这里即可)
                        if (resource != null && url == null && mapperClass == null) {
                            ErrorContext.instance().resource(resource);
                            inputStream = Resources.getResourceAsStream(resource);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                            // ⽣成XMLMapperBuilder,并执行其parse方法
                            mapperParser.parse();
                        } else if (resource == null && url != null && mapperClass == null) {
                            ErrorContext.instance().resource(url);
                            inputStream = Resources.getUrlAsStream(url);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else {
                            if (resource != null || url != null || mapperClass == null) {
                                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                            }

                            Class<?> mapperInterface = Resources.classForName(mapperClass);
                            this.configuration.addMapper(mapperInterface);
                        }
                    }
                }

                return;
            }
        }
    }

//..

}
public class XMLMapperBuilder extends BaseBuilder {
 //..
    
    public void parse() {
        if (!this.configuration.isResourceLoaded(this.resource)) {
            // 解析mapper属性
            this.configurationElement(this.parser.evalNode("/mapper"));
            this.configuration.addLoadedResource(this.resource);
            this.bindMapperForNamespace();
        }

        this.parsePendingResultMaps();
        this.parsePendingCacheRefs();
        this.parsePendingStatements();
    }

    //..
    
    private void configurationElement(XNode context) {
        try {
            String namespace = context.getStringAttribute("namespace");
            if (namespace != null && !namespace.equals("")) {
                this.builderAssistant.setCurrentNamespace(namespace);
                this.cacheRefElement(context.evalNode("cache-ref"));
                // 最终在这⾥看到了关于cache属性的处理
                this.cacheElement(context.evalNode("cache"));
                this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                this.resultMapElements(context.evalNodes("/mapper/resultMap"));
                this.sqlElement(context.evalNodes("/mapper/sql"));
                // 这⾥会将⽣成的Cache包装到对应的MappedStatement
                this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
            } else {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
        } catch (Exception var3) {
            throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
        }
    }
    //..
    private void cacheElement(XNode context) {
        if (context != null) {
            //解析<cache/>标签的type属性,这⾥我们可以自定义cache的实现类,⽐如redisCache,如果没有自定义,这⾥使用和一级缓存相同的PERPETUAL
            /*
              public String getStringAttribute(String name, String def) {
        String value = this.attributes.getProperty(name);
        return value == null ? def : value; //有的话,就操作我们设置的,否则默认就是PERPETUAL(def变量)
    }

            */
            String type = context.getStringAttribute("type", "PERPETUAL");
            Class<? extends Cache> typeClass = this.typeAliasRegistry.resolveAlias(type);
            String eviction = context.getStringAttribute("eviction", "LRU");
            Class<? extends Cache> evictionClass = this.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对象
            this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
        }

    }
    //..
}
先来看看是如何构建Cache对象的:
public class MapperBuilderAssistant extends BaseBuilder {
//..
 public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) {
     //⽣成Cache对象,他一般代表SynchronizedCache,但是最终还是会到PerpetualCache,虽然他只是存在,但并不一定操作,其他的如SynchronizedCache最终的LoggingCache可能就是操作唯一的key的
     Cache cache = (new CacheBuilder(this.currentNamespace)).implementation((Class)this.valueOrDefault(typeClass, PerpetualCache.class)).addDecorator((Class)this.valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();
     //添加到Configuration中
        this.configuration.addCache(cache);
        this.currentCache = cache; //还进行了赋值
        return cache;
    }

//..


}
我们看到一个Mapper.xml只会解析一次标签,也就是只创建一次Cache对象,放进configuration中, 并将cache赋值给MapperBuilderAssistant.currentCache
我们回到这里:
 // 最终在这⾥看到了关于cache属性的处理
                this.cacheElement(context.evalNode("cache"));
                this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                this.resultMapElements(context.evalNodes("/mapper/resultMap"));
                this.sqlElement(context.evalNodes("/mapper/sql"));
                // 这⾥会将⽣成的Cache包装到对应的MappedStatement
                this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
进入最后的操作:
//   XMLMapperBuilder类里面的
private void buildStatementFromContext(List<XNode> list) {
        if (this.configuration.getDatabaseId() != null) {
            this.buildStatementFromContext(list, this.configuration.getDatabaseId());
        }

        this.buildStatementFromContext(list, (String)null);
    }


//无论对应的判断是否为null,都会到这里来
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        Iterator var3 = list.iterator();

        while(var3.hasNext()) {
            XNode context = (XNode)var3.next();
            XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);

            try {
                // 每一条执行语句转换成一个MappedStatement
                statementParser.parseStatementNode();
            } catch (IncompleteElementException var7) {
                this.configuration.addIncompleteStatement(statementParser);
            }
        }

    }


public class XMLStatementBuilder extends BaseBuilder {
    
    //..

public void parseStatementNode() {
        String id = this.context.getStringAttribute("id");
        String databaseId = this.context.getStringAttribute("databaseId");
        if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            String nodeName = this.context.getNode().getNodeName();
            SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
            boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
            boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
            XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
            includeParser.applyIncludes(this.context.getNode());
            String parameterType = this.context.getStringAttribute("parameterType");
            Class<?> parameterTypeClass = this.resolveClass(parameterType);
            String lang = this.context.getStringAttribute("lang");
            LanguageDriver langDriver = this.getLanguageDriver(lang);
            this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
            String keyStatementId = id + "!selectKey";
            keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
            Object keyGenerator;
            if (this.configuration.hasKeyGenerator(keyStatementId)) {
                keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
            } else {
                keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            }

            SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
            StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
            Integer fetchSize = this.context.getIntAttribute("fetchSize");
            Integer timeout = this.context.getIntAttribute("timeout");
            String parameterMap = this.context.getStringAttribute("parameterMap");
            String resultType = this.context.getStringAttribute("resultType");
            Class<?> resultTypeClass = this.resolveClass(resultType);
            String resultMap = this.context.getStringAttribute("resultMap");
            String resultSetType = this.context.getStringAttribute("resultSetType");
            ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
            if (resultSetTypeEnum == null) {
                resultSetTypeEnum = this.configuration.getDefaultResultSetType();
            }

            String keyProperty = this.context.getStringAttribute("keyProperty");
            String keyColumn = this.context.getStringAttribute("keyColumn");
            String resultSets = this.context.getStringAttribute("resultSets");
            // 创建MappedStatement对象
            this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
        }
    }
    
    //..
    
}


public class MapperBuilderAssistant extends BaseBuilder {
    
    //..

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 (this.unresolvedCacheRef) {
            throw new IncompleteElementException("Cache-ref not yet resolved");
        } else {
            id = this.applyCurrentNamespace(id, false);
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            //创建MappedStatement对象
            org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache);
            // 上面最后的.cache(this.currentCache);在这⾥将之前⽣成的Cache封装到MappedStatement
            
            ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
            if (statementParameterMap != null) {
                statementBuilder.parameterMap(statementParameterMap);
            }

            MappedStatement statement = statementBuilder.build();
            this.configuration.addMappedStatement(statement);
            return statement;
        }
    }

//..
}
我们看到将Mapper中创建的Cache对象,加入到了每个MappedStatement对象中,也就是同一个Mapper中,即的确相同Mapper中的MappedStatement共用一个Cache(该一个Cache),当然了源码只是大致的给出说明,具体为什么需要深入看很多,所以了解即可
现在我们直接找到之前的如下:
//public abstract class BaseExecutor implements Executor {,不是这一个的,这是因为在操作二级缓存时,他不会操作他的,或者说对应的对象发生了改变,具体为什么,那么需要很多代码的查看,这里就不给出了,可以认为最终之前的缓存读取会改变这个对象即可,既然改变了,那么也就说明之前的clearLocalCache()是不同的,即对应的this.localOutputParameterCache.clear();大概率不是二级缓存,且也就基本不是resultHandler的原因了
/*
具体原因是之前的:
this.configuration.newExecutor(tx, execType);里面的
if (this.cacheEnabled) { //操作二级缓存,就会操作如下,并给出原来的在这之前赋值的executor
            executor = new CachingExecutor((Executor)executor);
        }
        可以全局搜索
*/
    public class CachingExecutor implements Executor {
    //..
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return this.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中
            // 我们在上面分析过每一个MappedStatement都有一个相同Cache对象,就是这⾥
        Cache cache = ms.getCache();
            // 如果配置文件中没有配置 <cache>,则 cache 为空
        if (cache != null) {
            //如果需要刷新缓存的话就刷新:flushCache="true"(实际上是清空,最终也会导致一级缓存清空的,在执行操作之前进行清空)
            /*
            对应的一级缓存对应的操作中存在这个(后面的this.delegate.query)
             if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache(); //也会清空
            }
            */
            this.flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, boundSql);
                // 访问⼆级缓存,注意是操作tcm的,即二级缓存最终是tcm操作,或者说是存放在tcm中
                List<E> list = (List)this.tcm.getObject(cache, key);
                // 缓存未命中
                if (list == null) {
                    // 如果没有值,则执行查询,这个查询实际也是先⾛一级缓存查询,一级缓存也没有的话,则进行DB查询
                    //this.delegate就是原来的executor,对应于executor = new BatchExecutor(this, transaction);,也更加的证明了,之前说明的二级缓存的确是上层的,并且一级缓存那里的确没有操作二级缓存
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    // 缓存查询结果
                    this.tcm.putObject(cache, key, list);
                }

                return list;
            }
        }

        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

    //..
     private void flushCacheIfRequired(MappedStatement ms) {
        Cache cache = ms.getCache();
        if (cache != null && ms.isFlushCacheRequired()) {
            this.tcm.clear(cache); //清除缓存
        }

    }
        //..
    
}
如果设置了flushCache=“true”,则每次查询都会刷新缓存
<!-- 执行此语句清空缓存(包括一级缓存和二级缓存),flushCache默认为false-->
<!--比如:-->
<select id="findbyId" resultType="com.lagou.pojo.user" useCache="true" 
flushCache="true">
 select * from user
</select>
如上,注意二级缓存是从 MappedStatement 中获取的,由于 MappedStatement 存在于全局配置 中,若多个 CachingExecutor 获取到,那么容易出现线程安全问题(比如其中一个导致清除缓存,另外一个就没有了,或者他们同时获取,使得都没有操作缓存),除此之外,若不加以控制,多个 事务共用一个缓存实例,会导致脏读问题(其中一个事务改变缓存,导致另外一个事务读取了你改变的,而不是他原本的,且他没有提交),⾄于脏读问题,需要借助其他类来处理,也就是上面代码中 tcm 变量对应的类型(二级缓存最终是tcm操作,或者说是存放在tcm中),下面分析一下
// private final TransactionalCacheManager tcm = new TransactionalCacheManager();

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

    public TransactionalCacheManager() {
    }

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

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

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

    public void commit() {
        Iterator var1 = this.transactionalCaches.values().iterator();

        while(var1.hasNext()) {
            TransactionalCache txCache = (TransactionalCache)var1.next();
            txCache.commit();
        }

    }

    public void rollback() {
        Iterator var1 = this.transactionalCaches.values().iterator();

        while(var1.hasNext()) {
            TransactionalCache txCache = (TransactionalCache)var1.next();
            txCache.rollback();
        }

    }

    private TransactionalCache getTransactionalCache(Cache cache) {
        // 从映射表中获取 TransactionalCache
        // TransactionalCache 也是一种装饰类,为 Cache 增加事务功能
        // 创建一个新的TransactionalCache,并将真正的Cache对象存进去,传递的参数Cache只是用来对应真正的Cache的
        return (TransactionalCache)this.transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
    }
}

TransactionalCacheManager 内部维护了 Cache 实例与 TransactionalCache 实例间的映射关系,该类 也仅负责维护两者的映射关系,真正做事的还是 TransactionalCache,TransactionalCache 是一种缓 存装饰器,可以为 Cache 实例增加事务功能,在之前提到的脏读问题正是由该类进行处理的,下面分析一下该类的逻辑
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
    //真正的缓存对象,和上面的Map<Cache, TransactionalCache>中的Cache是同一个
    private final Cache delegate;
    private boolean clearOnCommit;
    // 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
    private final Map<Object, Object> entriesToAddOnCommit;
    // 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中,单纯的保存key,来操作上面的entriesToAddOnCommit的
    private final Set<Object> entriesMissedInCache;

    public TransactionalCache(Cache delegate) {
        this.delegate = delegate;
        this.clearOnCommit = false;
        this.entriesToAddOnCommit = new HashMap();
        this.entriesMissedInCache = new HashSet();
    }

    public String getId() {
        return this.delegate.getId();
    }

    public int getSize() {
        return this.delegate.getSize();
    }

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

        return this.clearOnCommit ? null : object;
    }

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

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

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

    public void commit() {
        // 根据 clearOnCommit 的值决定是否清空 delegate
        if (this.clearOnCommit) {
            this.delegate.clear();
        }

        // 刷新未缓存的结果到 delegate 缓存中
        this.flushPendingEntries();
        // 重置 entriesToAddOnCommit 和 entriesMissedInCache
        this.reset();
    }

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

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

    private void flushPendingEntries() {
        Iterator var1 = this.entriesToAddOnCommit.entrySet().iterator();

        while(var1.hasNext()) {
            Entry<Object, Object> entry = (Entry)var1.next();
            // 将 entriesToAddOnCommit 中的内容转存到 delegate 中
            this.delegate.putObject(entry.getKey(), entry.getValue());
        }

        var1 = this.entriesMissedInCache.iterator();

        while(var1.hasNext()) {
            Object entry = var1.next();
            if (!this.entriesToAddOnCommit.containsKey(entry)) {
                // 存入空值
                this.delegate.putObject(entry, (Object)null);
            }
        }

    }

    private void unlockMissedEntries() {
        Iterator var1 = this.entriesMissedInCache.iterator();

        while(var1.hasNext()) {
            Object entry = var1.next();

            try {
                // 调用 removeObject 进行解锁
                this.delegate.removeObject(entry);
            } catch (Exception var4) {
                log.warn("Unexpected exception while notifiying a rollback to the cache adapter. Consider upgrading your cache adapter to the latest version. Cause: " + var4);
            }
        }

    }
    
}
存储二级缓存对象的时候是放到了TransactionalCache.entriesToAddOnCommit这个map中,但是每 次查询的时候是直接从TransactionalCache.delegate中去查询的(他就是另外一个map,即二级缓存),这个二级缓存查询数据库后,设 置缓存值是没有⽴刻⽣效的,主要是因为直接存到 delegate 会导致脏数据问题,所以可以看到putObject方法并没有存放在delegate中,这也就解决了脏读问题
既然存放不会放在对应的delegate 中,那么怎么存放呢,这就是一级缓存刷新到二级缓存的原因了,当然,这个怎么解决脏读问题呢
一般sqlSession.commit或者sqlSession.close执行后,那么一级缓存中内容才会刷新到二级缓存
为何只有SqlSession提交或关闭之后才会进行刷新到二级缓存呢,我们看如下:
public class DefaultSqlSession implements SqlSession {
 //..
    
        public void commit(boolean force) {
        try {
            // 主要是这句
            this.executor.commit(this.isCommitOrRollbackRequired(force));
            this.dirty = false;
        } catch (Exception var6) {
            throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + var6, var6);
        } finally {
            ErrorContext.instance().reset();
        }

    }

    //..
    
    
}


public class CachingExecutor implements Executor {
    
 //..
      public void commit(boolean required) throws SQLException {
        this.delegate.commit(required);
        this.tcm.commit(); //这里
    }
    
    //..
}

//TransactionalCacheManager类里面的
 public void commit() {
        Iterator var1 = this.transactionalCaches.values().iterator();

        while(var1.hasNext()) {
            TransactionalCache txCache = (TransactionalCache)var1.next();
            txCache.commit();
        }

    }

//TransactionalCache
public void commit() {
        if (this.clearOnCommit) {
            this.delegate.clear(); //清空
        }

        this.flushPendingEntries(); //刷新,当然,他内部是将原来保存的放入delegate中,而这样如此,其他事务操作时会操作他的,但是要注意:他是提交的,自然也就导致其他事务获取了他,所以也就解决了脏读(读已提交)
        this.reset();
    }

//..
private void flushPendingEntries() {
        Iterator var1 = this.entriesToAddOnCommit.entrySet().iterator();

        while(var1.hasNext()) {
            Entry<Object, Object> entry = (Entry)var1.next();
            // 在这⾥真正的将entriesToAddOnCommit的对象逐个添加到delegate中,只有这时,二级缓存才真正的⽣效
            this.delegate.putObject(entry.getKey(), entry.getValue());
        }

        var1 = this.entriesMissedInCache.iterator();

        while(var1.hasNext()) {
            Object entry = var1.next();
            if (!this.entriesToAddOnCommit.containsKey(entry)) {
                this.delegate.putObject(entry, (Object)null);
            }
        }

    }
二级缓存的清除:
一般情况下,增删改会清空缓存,包括一级和二级
比如我们可以看看更新操作:
//DefaultSqlSession
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;
    }

//CachingExecutor
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);
        }

    }
可以发现,更新操作不产生二级缓存,但是会清除二级缓存(前提是有提交,根据源码看就知道了),经过测试,在增删改中flushCache默认不是false,而是true
所以说,我们都会认为增删改是会清空一级缓存和二级缓存的
通过前面的源码分析,可以知道:
增删改都会清空一级和二级缓存,若设置了flushCache为false(增删改默认为true的),那么二级缓存不会清空,一级缓存是他自身就会清空,与flushCache没有关系,所以二级缓存还有余地(只要提交了修改的,就会进行清空(这个二级删除的是entriesToAddOnCommit),他会使得提交操作可以真正的删除二级缓存,只有提交后,才会真正删除二级缓存,而增删改一般我们都会提交,所以这里就默认清空二级缓存了)
若出现提交和关闭(二级缓存关闭操作里面一般会提交后关闭的,一般不会回滚(因为对应的return的其判断基本都是false),或者需要某些条件,具体可以百度,而一级缓存则会清空,包括提交也是),那么一级缓存会到二级缓存,并会清空一级缓存,而操作到二级缓存的前提是一级缓存(该一级缓存是对应缓存类的查询操作出来的,并不是真的一级缓存,或者说就是查询的结果,但是由于查询必然会产生一级缓存,所以可以这样说明)存在,而由于增删改没有一级缓存或者说缓存的操作,所以增删改的提交和关闭不会到二级缓存中,而查询就会,其中查询默认flushCache为false,查询与flushCache有关
其中SqlSession的clearCache方法一般只能清空一级缓存
可以发现MyBatis二级缓存只适用于不常进行增、删、改的数据,⽐如国家行政区省市区街道数据,一但数据变更,MyBatis会清空缓存,因此二级缓存不适用于经常进行更新的数据,那么为什么二级缓存还有余地呢,很明显,这个余地是给查询的,且是二级缓存(因为增删改没有一级,自然没有对应的缓存),说明你的清空还要考虑其他地方操作的缓存,因为你的操作总不能让我再次的查询的,但是一般我们也不会这样,因为虽然不会查询,但是数据基本不好对,所以增删改默认flushCache为true的,使得一直存在删除二级缓存的条件,而由于我们基本都会进行提交,所以,我们认为他就是删除了二级缓存
总结:
在二级缓存的设计上,MyBatis大量地运用了装饰者模式,如CachingExecutor,以及各种Cache接口的装饰器,该模式就是给出相同接口,并给出要装饰的值作为参数给他,并进行增强,他与代理模式不同的是:代理是一个新的类,并且该类里面是操作创建要代理的类的,而不是作为参数,而装饰器则是作为参数,一般来说装饰器就是合成复用原则的基础上,进行操作的增强,而代理就是工厂模式上进行的增强,只是这个工厂一般在构造方法中操作,我们回顾jdk代理,我们可以知道他需要传递一个接口的class,并生成对应的代理类,实际上该代理类有着对应的所有方法,并可以增强的操作,但是对方却是接口,而不是类,那么还能说是代理吗,答:可以,代理主要是增强,如果是类,我们创建即可,如果是接口,我们实现即可,而装饰器就是作为参数的
但是他们的区别并不大,那么为什么要这样的设计呢:这里给出原因,因为装饰器是装饰的,由于是参数,所以可以被多个装饰器装饰,重要的就是装饰,而代理是创建,那么只能由你一个人操作,也就是代你操作了,而不是装饰的被多人操作,即代理给一个人,装饰给多个人,代理重在自身的增强,而装饰考虑多方的增强,也就是说,如果以后有三级缓存,四级缓存,那么我们继续装饰即可,从四级开始,而代理就不能,那么代理的好处是什么,他的好处是他可以给任何操作该类的地方进行增强,而不用再次的创建装饰器,若有很多地方使用,而我只需要改动一处,而装饰器需要给对应的地方都进行创建装饰器,因为各个模块总不能都有相同的类吧,所以各有好处,简单来说,跨项目基本最好使用代理,而单纯的项目,可以利用装饰器(因为类可以合成复用)
但是我们建议最好多利用代理:因为代码是最少的,虽然在扩展上装饰器比较强大,但是跨模板不友好,需要空间多
总之:各有好处
二级缓存的完成说明:
二级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
二级缓存具有丰富的缓存策略
二级缓存可由多个装饰器,与基础缓存组合而成
二级缓存工作由 一个缓存装饰执行器CachingExecutor和 一个事务型预缓存TransactionalCache 完成
实际上mybatis对细节的操作还是有很多的问题,比如,在select中操作delete语句,除了select的设置外,语句会执行的,当然,由于大多数操作,如二级缓存等等基本看标签,所以也会导致delete的语句进行了保存,即操作了二级缓存,使得二级缓存也保留了了增删改的操作,这是不好的情况(没有想到),简单来说,标签只是设置一些属性的地方,并不代表语句一定是标签的语句,这也是mybatis的一个细节问题,当然了,随着时间的推移,可能在以后的版本中,会进行修改(如判断语句是否是对应标签的对应语句),或者不用修改,因为我们只要根据规范编写即可,当然,根据我的测试,一般语句和标签不对,他就好像有特殊的操作,使得可能不执行,并返回null或者说规定的返回值(如增删改可能是-1)给缓存(一般增删改没有,只有赋值,而查询两者都有)或者进行赋值,所以我们也不要对这些问题进行太过在意,因为他还是解决的了,只不过并不是报错的解决,而是兜底的解决
延迟加载源码剖析:
在63章博客中,也说明了延迟缓存,但是并没有说明源码(在这里可能会改变对应63章博客的说明,因为这里是按照源码来分析的),所以现在来进行说明,至于配置文件中的某些xml,如动态sql,关联查询等等,实际上就是读取xml解析的过程(一般也不考虑顺序,除非有对应的约束,但是读取一般不用考虑,因为可以直接通过名称获取即可),所以并不需要剖析,即,我们主要看最重要的几个即可,当然,他们有些时候是有限制的,一般情况下,我们规范的写即可,比如关联collection中的column参数只有一个,那么得到的也最好写一个,否则可能报错(关联查询),当然,一般情况下,对应的参数都是传递的一个的值,这里与其他不关联的是不同的,即非常特殊,自己可以进行测试
什么是延迟加载?
在开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息,此时就是我 们所说的延迟加载,也就是并不需要加载某些东西
延迟加载原理实现:
它的原理是,使用 CGLIB 或 Javassist( 默认 ) 创建目标对象的代理对象,当调用代理对象的延迟加载属 性的 getting 方法时,进入拦截器方法,⽐如调用 a.getB().getName() 方法,进入拦截器的 invoke(…) 方法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对象的 SQL ,把 B 查询上来,然后调用 a.setB(b) 方法,于是 a 对象 b 属性就有值了,接着完 成 a.getB().getName() 方法的调用,这就是延迟加载的基本原理
总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定方法,执行数据加载,而对应的配置就是保存关联代码的操作,简单来说,就是先保存sql,然后在操作对应方法时进行执行,并进行赋值操作,当然,一般会保留原来赋值的变量或者操作来进行获取的
延迟加载原理(源码剖析):
MyBatis延迟加载主要使用:Javassist,Cglib实现,类图展示:

在这里插入图片描述

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.ibatis.executor.loader;

/** @deprecated */
@Deprecated
public final class JavassistProxyFactory extends org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory {
    public JavassistProxyFactory() {
    }
}
//org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory
public class JavassistProxyFactory implements ProxyFactory {
    //..
}

@Deprecated
public class CglibProxyFactory extends org.apache.ibatis.executor.loader.cglib.CglibProxyFactory {
    public CglibProxyFactory() {
    }
}

//org.apache.ibatis.executor.loader.cglib.CglibProxyFactory
public class CglibProxyFactory implements ProxyFactory {
 //..   
}

public interface ProxyFactory {
    default void setProperties(Properties properties) {
    }

    Object createProxy(Object var1, ResultLoaderMap var2, Configuration var3, ObjectFactory var4, List<Class<?>> var5, List<Object> var6);
}


Setting 配置加载:
public class Configuration {

//..
    //aggressiveLazyLoading:
    /*
    当开启时,任何方法的调用都会加载该对象的所有延迟加载属性,否则,每个属性会按需加载(参考lazyLoadTriggerMethods)
  <settings>
        <!--开启全局延迟加载功能,那么就不用添加fetchType="lazy"来启动延迟加载了-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--当调用对应的toString()方法时,不进行延迟加载-->
        <setting name="lazyLoadTriggerMethods" value="toString"/>
    </settings>
    换句话说,如果没有设置,也就是说没有lazyLoadTriggerMethods,那么默认对应的四个属性都操作延迟的,而加上了lazyLoadTriggerMethods那么设置的如上面的toString不延迟加载了,即lazyLoadTriggerMethods可以使得被的sql不用保存,但他也默认存在对应的四个,所以说,这个true相当于默认的lazyLoadTriggerMethods,而lazyLoadTriggerMethods导致他为false,但他由于是默认的四个,所以通常如果没有设置,那么还是相同的,而只有设置了才不会
    */
    //默认为true
    protected boolean aggressiveLazyLoading;
    //..
   public Configuration() {
    //..
    this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString"));
    
    //..
       //是否开启延迟加载,默认是false的
       this.lazyLoadingEnabled = false;
       //..
   }
    
    //..
    //默认使用Javassist代理工厂
    public void setProxyFactory(ProxyFactory proxyFactory) {
        if (proxyFactory == null) {
            proxyFactory = new JavassistProxyFactory();
        }

        this.proxyFactory = (ProxyFactory)proxyFactory;
    }
    
    //..
}
延迟加载代理对象创建:
Mybatis的查询结果是由ResultSetHandler接口的handleResultSets()方法处理的(具体在前面说明过了,可以全局搜索),ResultSetHandler接口只有一个实现,DefaultResultSetHandler,接下来看下延迟加载相关的一个核心的方法
public class DefaultResultSetHandler implements ResultSetHandler {

    //..
    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
        this.useConstructorMappings = false;
        List<Class<?>> constructorArgTypes = new ArrayList();
        List<Object> constructorArgs = new ArrayList();
        //创建返回的结果映射的真实对象,比如User的对象(相当于new User(),没有任何的赋值),他主要用来给赋值的,但不是这里,是上层方法getRowValue进行的后续操作(自己调试即可)
        Object resultObject = this.createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
        if (resultObject != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
            Iterator var9 = propertyMappings.iterator();

            while(var9.hasNext()) {
                ResultMapping propertyMapping = (ResultMapping)var9.next();
                // 判断属性有没配置嵌套查询,如果有就创建代理对象,propertyMapping.isLazy()看看有没有设置对应的属性fetchType="lazy",一般只有特点的标签才有他哦,否则报错(约束,collection和association一般可以)
                if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
                    //创建延迟加载代理对象
                    resultObject = this.configuration.getProxyFactory().createProxy(resultObject, lazyLoader, this.configuration, this.objectFactory, constructorArgTypes, constructorArgs);
                    break;
                }
            }
        }

        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty();
        return resultObject;
    }
    
    //创建返回的结果映射的真实对象
    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) throws SQLException {
        Class<?> resultType = resultMap.getType();
        MetaClass metaType = MetaClass.forClass(resultType, this.reflectorFactory);
        List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
        if (this.hasTypeHandlerForResultObject(rsw, resultType)) {
            return this.createPrimitiveResultObject(rsw, resultMap, columnPrefix);
        } else if (!constructorMappings.isEmpty()) {
            return this.createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
        } else if (!resultType.isInterface() && !metaType.hasDefaultConstructor()) {
            if (this.shouldApplyAutomaticMappings(resultMap, false)) {
                return this.createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
            } else {
                throw new ExecutorException("Do not know how to create an instance of " + resultType);
            }
        } else {
            return this.objectFactory.create(resultType);
        }
    }
    //..
}


//上面的最终方法是前面的this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);里面的,自己可以进行调试(前面并没有说明)
默认采用javassistProxy进行代理对象的创建:
 //创建延迟加载代理对象,主要看看createProxy
                    resultObject = this.configuration.getProxyFactory().createProxy(resultObject, lazyLoader, this.configuration, this.objectFactory, constructorArgTypes, constructorArgs);

//Configuration的
 public ProxyFactory getProxyFactory() {
        return this.proxyFactory; //在前面proxyFactory = new JavassistProxyFactory();
    }
所以我们看看这个JavassistProxyFactory类:
public class JavassistProxyFactory implements ProxyFactory {
 //..
    /*
    target 目标结果对象
    lazyLoader 延迟加载对象
    configuration 配置
    objectFactory 对象工厂
    constructorArgTypes 构造参数类型
    constructorArgs 构造参数值
    */
      public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
          //进入
        return JavassistProxyFactory.EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
    }

    //..
    static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        org.apache.ibatis.javassist.util.proxy.ProxyFactory enhancer = new org.apache.ibatis.javassist.util.proxy.ProxyFactory();
        /*
        package org.apache.ibatis.javassist.util.proxy;
        不是对应的package org.apache.ibatis.executor.loader;里的ProxyFactory接口,这个接口才是操作我们的JavassistProxyFactory的
        public class ProxyFactory {
        //..
        }
        */
        enhancer.setSuperclass(type);

        try {
            //通过获取对象方法,判断是否存在该方法
            type.getDeclaredMethod("writeReplace");
            if (JavassistProxyFactory.LogHolder.log.isDebugEnabled()) {
                JavassistProxyFactory.LogHolder.log.debug("writeReplace method was found on bean " + type + ", make sure it returns this");
            }
        } catch (NoSuchMethodException var10) {
            //没找到该方法,实现接口
            enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class});
        } catch (SecurityException var11) {
        }

        Class<?>[] typesArray = (Class[])constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
        Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);

        Object enhanced;
        try {
            //创建新的代理对象
            enhanced = enhancer.create(typesArray, valuesArray);
        } catch (Exception var9) {
            throw new ExecutorException("Error creating lazy proxy.  Cause: " + var9, var9);
        }

        //设置代理执行器
        ((Proxy)enhanced).setHandler(callback);
        return enhanced; //返回的他最终会操作invoke方法的
    }
    //..
    //上面最终到这里:
        private static class EnhancedResultObjectProxyImpl implements MethodHandler {
            
            //..
              private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
            this.type = type;
            this.lazyLoader = lazyLoader;
            this.aggressive = configuration.isAggressiveLazyLoading();
            this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
            this.objectFactory = objectFactory;
            this.constructorArgTypes = constructorArgTypes;
            this.constructorArgs = constructorArgs;
        }
            /*
            target 目标结果对象
            lazyLoader 延迟加载对象
            configuration 配置	
            objectFactory 对象工厂
            constructorArgTypes 构造参数类型
            constructorArgs 构造参数值
            */
            public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
            Class<?> type = target.getClass();
                //初始化
            JavassistProxyFactory.EnhancedResultObjectProxyImpl callback = new JavassistProxyFactory.EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
                //操作前面的crateProxy
            Object enhanced = JavassistProxyFactory.crateProxy(type, callback, constructorArgTypes, constructorArgs);
            PropertyCopier.copyBeanProperties(type, target, enhanced);
            return enhanced;
        }
            
            //操作是否延迟的主要操作,考虑保留sql,以后继续执行的
             public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
            String methodName = method.getName();

            try {
                synchronized(this.lazyLoader) {
                    if ("writeReplace".equals(methodName)) {
                        //忽略暂未找到具体作用
                        Object original;
                        if (this.constructorArgTypes.isEmpty()) {
                            original = this.objectFactory.create(this.type);
                        } else {
                            original = this.objectFactory.create(this.type, this.constructorArgTypes, this.constructorArgs);
                        }

                        PropertyCopier.copyBeanProperties(this.type, enhanced, original);
                        if (this.lazyLoader.size() > 0) {
                            return new JavassistSerialStateHolder(original, this.lazyLoader.getProperties(), this.objectFactory, this.constructorArgTypes, this.constructorArgs);
                        }

                        return original;
                    }

                    //延迟加载数量大于0
                    if (this.lazyLoader.size() > 0 && !"finalize".equals(methodName)) {
                        //aggressive一次性加载所有延迟加载属性,代表之前的aggressiveLazyLoading的值,如果没有配置lazyLoadTriggerMethods,那么该值默认为true,而配置了(在配置类中操作,这里就不给出具体的代码了),那么他就是false,自然判断后面是否存在了,而考虑是否不操作延迟(这里会到true的,因为为false,即我们设置的主要是里面的操作,这里要注意)
                        if (!this.aggressive && !this.lazyLoadTriggerMethods.contains(methodName)) {
                            String property;
                            //判断是否为set方法,set方法不需要延迟加载
                            if (PropertyNamer.isSetter(methodName)) {
                                property = PropertyNamer.methodToProperty(methodName);
                                this.lazyLoader.remove(property);
                            } else if (PropertyNamer.isGetter(methodName)) {
                                property = PropertyNamer.methodToProperty(methodName);
                                if (this.lazyLoader.hasLoader(property)) {
                                    //延迟加载单个属性,即真的执行的地方
                                    this.lazyLoader.load(property);
                                }
                            }
                        } else {
                            //一次全部加载
                            this.lazyLoader.loadAll();
                        }
                    }
                }
//执行原来的方法
                return methodProxy.invoke(enhanced, args);
            } catch (Throwable var10) {
                throw ExceptionUtil.unwrapThrowable(var10);
            }
        }
            
        }
    
    //..
    
}
//对应的上层getRowValue里面的foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
          //是invoke执行的地方,他是从而判断是否延迟的主要操作(自己调试就知道了)
上面就给出了大致的说明了,至此延迟加载大致说明完毕
设计模式:
我们可以知道大多数的设计模式,如有3类23种设计模式,但是我们通常都不会刻意的去记住,因为设计模式无非就是对某种操作的简称而已,一般情况下,我们会根据场景来进行操作,可能你的操作就是某种设计模式而不自知,Mybatis源码中使用了大量的设计模 式,观察设计模式在其中的应用,能够更深入的理解设计模式
Mybatis至少用到了以下的设计模式的使用:

在这里插入图片描述

这里要特别注意:设计模式有些没有使用到的,可以不用去记住,只要你认为你的操作比较好就行了
大多数情况下,我们在mybatis中会优先记住如下设计模式:
Builder构建者模式、工厂模式、代理模式,现在我们来进行解读,先介绍模式自身的知识,然后解读在 Mybatis中怎样应用了该模式
Builder构建者模式:
Builder模式的定义是"将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示",它属于创建类的模式(会创建类对象的模式都可以称为创建类模式,如工厂模式,还有构建者模式),一般来说,如果一个对象的构建⽐较复杂,超出了构造函数所能包含的范围,就可以使用工厂模式和Builder模式,相对于工厂模式会产出一个完整的产品,Builder应用于更加复杂的对象的构建,甚⾄只会构建产品的一个部分,直⽩来说,就是使用多个简单的对象一步一步构建 成一个复杂的对象
为了区分工厂模式与他的区别,现在我们来看看例子
例⼦:使用构建者设计模式来⽣产computer(电脑)
主要步骤:
1:将需要构建的目标类分成多个部件(电脑可以分为主机、显示器、键盘、鼠标等部件)
2:创建构建类
3:依次创建部件
4:将部件组装成目标对象
定义computer:
package test;

/**
 *
 */
public class Computer {
    private String displayer; //显示器
    private String mainUnit; //主机
    private String mouse; //鼠标
    private String keyboard; //键盘

    public String getDisplayer() {
        return displayer;
    }

    public void setDisplayer(String displayer) {
        this.displayer = displayer;
    }

    public String getMainUnit() {
        return mainUnit;
    }

    public void setMainUnit(String mainUnit) {
        this.mainUnit = mainUnit;
    }

    public String getMouse() {
        return mouse;
    }

    public void setMouse(String mouse) {
        this.mouse = mouse;
    }

    public String getKeyboard() {
        return keyboard;
    }

    public void setKeyboard(String keyboard) {
        this.keyboard = keyboard;
    }

    @Override
    public String toString() {
        return "computer{" +
                "displayer='" + displayer + '\'' +
                ", mainUnit='" + mainUnit + '\'' +
                ", mouse='" + mouse + '\'' +
                ", keyboard='" + keyboard + '\'' +
                '}';
    }
}

ComputerBuilder(构建类):
package test;

/**
 *
 */
public class ComputerBuilder {
    private Computer target = new Computer();

    public void installDisplayer(String displayer) {
        target.setDisplayer(displayer);
    }


    public void installMainUnit(String mainUnit) {
        target.setMainUnit(mainUnit);
    }


    public void installMouse(String mouse) {
        target.setMouse(mouse);
    }


    public void installKeybord(String keyboard) {
        target.setKeyboard(keyboard);
    }


    public Computer build() {
        return target;
    }
}

调用:
package test;

/**
 *
 */
public class test {

    public static void main(String[] args) {
        ComputerBuilder computerBuilder = new ComputerBuilder();
        computerBuilder.installDisplayer("显万器");
        computerBuilder.installMainUnit("主机");
        computerBuilder.installKeybord("键盘");
        computerBuilder.installMouse("⿏标");
        Computer computer = computerBuilder.build();
        System.out.println(computer);
    }
}

简单来说,构建者模式与工厂模式的区别就是,构建者模式相当于工厂模式准备版本,工厂模式不是准备,而是直接的返回,可以通过构造方法来进行设置,而构建者模式很明显是实现准备一个对象,然后进行设置,总体来说,构建者模式一开始是需要一个空间占用的,这是一个缺点,好处就是,我可以更好的扩展,而不用创建多个构造方法,且可以继续设置值,即修改,即扩展性大
所以,如果不需要考虑空间的话,尽量使用构建者模式,大多数我们都会使用构建者模式来代替工厂模式,当然了,如果没有什么赋值的操作,那么工厂模式就比较简单了,直接操作构造方法即可,但还是可以使用构建者模式,虽然执行代码会多一点(长远来说要少,因为构造方法的参数是一直多的,那么对于代码来说,参数少可以优先考虑工厂,参数多优先考虑构建者,对于空间来说,空间少,考虑工厂,空间多考虑构建者,由于我们尽量会考虑维护,所以我们以代码为标志,然后考虑空间)
注意:上面的优缺点是建立在单个对象的情况下,如果是多个对象,我建议直接使用工厂模式,因为对于构建者来说,总不能将所有的方法都放在一起吧,这是比较麻烦的事情,所以后面的工厂模式为了进行突出,所以特别操作两个类来表示示例
Mybatis中的体现:
SqlSessionFactory 的构建过程:
Mybatis的初始化工作⾮常复杂,有些情况参数非常多,不是只用一个构造函数就能搞定的,所以使用了建造者模式,使用了 大 量的Builder,进行分层构造,核心对象Configuration使用了 XmlConfigBuilder来进行构造

在这里插入图片描述

在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的Mybatis的sqlMapConfig.xml 和所有的 Mapper.xml 文件,构建 Mybatis 运行的核心对象 Configuration 对 象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象,其中你可以知道这个:
private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

这里你应该熟悉吧,这就是之前源码的说明
很明显,他操作了大多数的构建者工作
其中 XMLConfigBuilder 在构建 Configuration 对象时,也会调用 XMLMapperBuilder 用于读取 Mapper 文件,而XMLMapperBuilder会使用XMLStatementBuilder来读取和build所有的SQL语句
public void parse() {
        if (!this.configuration.isResourceLoaded(this.resource)) {
            this.configurationElement(this.parser.evalNode("/mapper")); //this.parser:    private final XPathParser parser;
            this.configuration.addLoadedResource(this.resource);
            this.bindMapperForNamespace();
        }

        this.parsePendingResultMaps();
        this.parsePendingCacheRefs();
        this.parsePendingStatements(); //如这里
    }
在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser 解析、配置或语法的解析、反射⽣成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此大量采用了 Builder模式来解决
即分开完成,最终得到Configuration 对象,SqlSessionFactoryBuilder类根据不同的输入参数来构建SqlSessionFactory这个工厂对象,我们都已输入流为主的说明
工厂模式:
在Mybatis中⽐如SqlSessionFactory使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂 模式
简单工厂模式(Simple Factory Pattern):⼜称为静态工厂方法(Static Factory Method)模式,它属于创建型(类)模式
既然是静态工厂,那么自然他是一个静态方法,大多数静态的说明都是来操作静态static关键字的
在简单工厂模式中,可以根据参数的不同返回不同类的实例,简单工厂模式专⻔定义一个类来负责创建 其他类的实例,被创建的实例通常都具有共同的⽗类(前提是被创建的)
例⼦:⽣产电脑
假设有一个电脑的代工⽣产商,它目前已经可以代工⽣产联想电脑了,随着业务的拓展,这个代工⽣产 商还要⽣产惠普的电脑,我们就需要用一个单独的类来专⻔⽣产电脑,这就用到了简单工厂模式
创建抽象产品类:
我们创建一个电脑的抽象产品类,他有一个抽象方法用于启动电脑:
package test1;

/**
 *
 */
public abstract class Computer {
    //产品的抽象方法,由具体的产品类去实现
    public abstract void start();
}

创建具体产品类:
接着我们创建各个品牌的电脑,他们都继承了他们的⽗类Computer,并实现了⽗类的start方法:
package test1;

/**
 *
 */
public class LenovoComputer extends Computer {
    @Override
    public void start() {
        System.out.println("联想电脑启动");
    }
}

package test1;

/**
 *
 */
public class HpComputer extends Computer {
    @Override
    public void start() {
        System.out.println("惠普电脑启动");
    }
}

创建工厂类:
接下来创建一个工厂类,它提供了一个静态方法createComputer用来⽣产电脑,你只需要传入你 想⽣ 产的电脑的品牌,它就会实例化相应品牌的电脑对象
package test1;

/**
 *
 */
public class ComputerFactory {
    public static Computer createComputer(String type) {
        Computer mComputer = null;
        switch (type) {
            case "lenovo":
                mComputer = new LenovoComputer();
                break;
            case "hp":
                mComputer = new HpComputer();
                break;
        }
        return mComputer;
    }
}

客户端调用工厂类:
package test1;

/**
 *
 */
public class CreatComputer {
    public static void main(String[] args) {
        Computer lenovo = ComputerFactory.createComputer("lenovo");
        System.out.println(lenovo);
        Computer hp = ComputerFactory.createComputer("hp");
        System.out.println(hp);
    }
}

Mybatis 体现:
Mybatis中执行Sql语句、获取Mappers、管理事务的核心接口SqlSession的创建过程使用到了工厂模 式
有一个 SqlSessionFactory 来负责 SqlSession 的创建,SqlSessionFactory 的openSession ()方法重载了很多个,分别⽀ 持autoCommit、Executor、Transaction等参数的输入,来构建核心的SqlSession对象
在DefaultSqlSessionFactory的默认工厂实现⾥,有一个方法可以看出工厂怎么产出一个产品:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            //根据参数创建制定类型的Executor
            Executor executor = this.configuration.newExecutor(tx, execType);
            //返回的是 DefaultSqlSession
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }
这是一个openSession调用的底层方法,该方法先从configuration读取对应的环境配置,然后初始化 TransactionFactory 获得一个 Transaction 对象,然后通过 Transaction 获取一个 Executor 对象,最后通过configuration、Executor、是否autoCommit三个参数直接构建了 SqlSession
代理模式:
代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用,代理模式的英文叫做Proxy,它是一种对象结构型模式,代理模式分为静态代理和动态代理,我们来介绍动态代理
举例:
创建一个抽象类,Person接口,使其拥有一个没有返回值的doSomething方法
package test2;

/**
 * 抽象类人
 */
public interface Person {
    void doSomething();
}

创建一个名为Bob的Person接口的实现类,使其实现doSomething方法
package test2;

/**
 *
 */
public class Bob implements Person {
    @Override
    public void doSomething() {
        System.out.println("你好,我的贵人");
    }
}

创建JDK动态代理类,使其实现InvocationHandler接口,拥有一个名为target的变量,并创建 getTarget获取代理对象方法
package test2;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JDK动态代理,需实现 InvocationHandler 接口
 */
public class JDKDynamicProxy implements InvocationHandler {
    //被代理的对象
    Person target;

    // JDKDynamicProxy 构造函数
    public JDKDynamicProxy(Person person) {
        this.target = person;
    }

    //获取代理对象
    public Person getTarget() {
        //可以写成this,因为有实现类,不用操作匿名内部类了,只是需要自己写invoke方法了
        return (Person) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    //动态代理invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //被代理方法前执行
        System.out.println("你好吗");
        //执行被代理的方法,自然也会给我们操作代理的返回了
        Person result = (Person) method.invoke(target, args);
        //被代理方法后执行
        System.out.println("我很好");
        return result; //执行对应代理方法后的返回值
    }
}

创建JDK动态代理测试类JDKDynamicTest:
package test2;

/**
 * JDK动态代理测试
 */
public class JDKDynamicTest {
    public static void main(String[] args) {
        System.out.println("不使用代理类,调用doSomething方法");
        //不使用代理类
        Person person = new Bob();
        // 调用 doSomething 方法
        person.doSomething();
        System.out.println("分割线-----------");
        System.out.println("使用代理类,调用doSomething方法");
        //获取代理类
        Person proxyPerson = new JDKDynamicProxy(new Bob()).getTarget();
        // 调用 doSomething 方法
        proxyPerson.doSomething();

    }
}

Mybatis中实现:
代理模式可以认为是Mybatis的核心使用的模式,正是由于这个模式,我们只需要编写Mapper接 口,不需要实现,由Mybatis后台帮我们完成具体SQL的执行
当我们使用Configuration的getMapper方法时,会调用mapperRegistry.getMapper方法,而该方法⼜ 会调用 mapperProxyFactory.newInstance(sqlSession)来⽣成一个具体的代理
public class MapperProxyFactory<T> {
      private final Class<T> mapperInterface;
    private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public Map<Method, MapperMethodInvoker> getMethodCache() {
        return this.methodCache;
    }

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

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
    
}
在这⾥,先通过T newInstance(SqlSession sqlSession)方法会得到一个MapperProxy对象,然后调用T newInstance(MapperProxy mapperProxy)⽣成代理对象然后返回,而查看MapperProxy的代码,可以看到如下内容:
public class MapperProxy<T> implements InvocationHandler, Serializable {
 //..
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }
    //..
    
}
⾮常典型的jdk代理模式,该MapperProxy类实现了InvocationHandler接口,并且实现了该接口的invoke方法,通 过这种方式,我们只需要编写Mapper接口类,当真正执行一个Mapper接口的时候,就会转发给 MapperProxy.invoke方法,而该方法则会调用后续的一系列方法,完成 SQL 的执行和返回,在前面也说明了这些代码,具体里面的实现可以选择继续看看
至此,我们的mybatis的底层原理大致都说明完毕,但是注解的并没有说明,实际上只是读取注解信息进行相同的赋值而已,所以就不说明了,具体去百度查看吧
当然只是具体说明mybatis的实现,因为mybatis可不是一个人就能完成的,自然不可能全部说明,因为那样太浪费时间了
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值