大晖带你学习mybatis源码解析

/ Java / 0 条评论 / 860浏览

MyBatis源码详解

这篇文章,我将从最原始的jdbc开始和大家一起讨论MyBatis(后面会穿插mp)的实现原理和设计思想,下面我们来一起来看吧

1. jdbc操作数据库

1.1 jdbc是什么

Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口;所以jdbc是一种技术规范,所有希望使用java语言操作的数据库必须提供一个操作的api程序,并且这个程序必须遵循jdbc的规范,所以无论什么数据库,mysql,oracle等等,在java语言中操作就是通过这个遵循了jdbc规范的程序来实现的;

1.2 jdbc详细解析

1.2.1 Driver,DriverManager,Statement,PrepareStatement

Driver接口其实就是java为我们规定的jdbc的规范,所有的数据库厂商如果希望提供一个本数据库的驱动程序,那么这个驱动程序类一定需要实现这个Driver接口(jdbc规范),就如Driver接口的官方注释中写的一样:

 * The interface that every driver class must implement.
 * <P>The Java SQL framework allows for multiple database drivers.
 *
 * <P>Each driver should supply a class that implements
 * the Driver interface.
 *
 * <P>The DriverManager will try to load as many drivers as it can
 * find and then for any given connection request, it will ask each
 * driver in turn to try to connect to the target URL.
 *
 * <P>It is strongly recommended that each Driver class should be
 * small and standalone so that the Driver class can be loaded and
 * queried without bringing in vast quantities of supporting code.
 *
 * <P>When a Driver class is loaded, it should create an instance of
 * itself and register it with the DriverManager. This means that a
 * user can load and register a driver by calling:
 * <p>
 * {@code Class.forName("foo.bah.Driver")}
 * <p>

也就是说,DriverManager叫做驱动管理器,所有导入进来的数据库驱动(遵循jdbc规范)都会被DriverManager自动搜索到并注册到管理器中;我们再来看一下DriverManager类的说明:

The basic service for managing a set of JDBC drivers

 <P>Applications no longer need to explicitly load JDBC drivers using <code>Class.forName()</code>. Existing programs
 * which currently load JDBC drivers using <code>Class.forName()</code> will continue to work without
 * modification.
 *
 * <P>When the method <code>getConnection</code> is called,
 * the <code>DriverManager</code> will attempt to
 * locate a suitable driver from amongst those loaded at
 * initialization and those loaded explicitly using the same classloader
 * as the current applet or application.
 *
 * <P>
 * Starting with the Java 2 SDK, Standard Edition, version 1.3, a
 * logging stream can be set only if the proper
 * permission has been granted.  Normally this will be done with
 * the tool PolicyTool, which can be used to grant <code>permission
 * java.sql.SQLPermission "setLog"</code>.

也就是说DriverManager引入后,我们获取数据库连接就可以直接使用DriverManager获取,不需要手动加载相应的驱动,比如现在我们引入了Oracle和MySQl的驱动,并且现在我们需要连接mysql,我们只需要指定配置好参数,直接使用DriverNanager获取连接,DriverMnager会自动帮我们加载合适的驱动并获取一个连接;

再来看一看Statement:

/**
 * <P>The object used for executing a static SQL statement
 * and returning the results it produces.
 * <P>
 * By default, only one <code>ResultSet</code> object per <code>Statement</code>
 * object can be open at the same time. Therefore, if the reading of one
 * <code>ResultSet</code> object is interleaved
 * with the reading of another, each must have been generated by
 * different <code>Statement</code> objects. All execution methods in the
 * <code>Statement</code> interface implicitly close a current
 * <code>ResultSet</code> object of the statement if an open one exists.
 *
 * @see Connection#createStatement
 * @see ResultSet
 */
public interface Statement extends Wrapper, AutoCloseable {

也就是说statement是sql语句的执行器,他可以执行静态sql语句并且返回执行的返回结果

下面是PrepareStatement

/**
 * An object that represents a precompiled SQL statement.
 * <P>A SQL statement is precompiled and stored in a
 * <code>PreparedStatement</code> object. This object can then be used to
 * efficiently execute this statement multiple times.
 *
 * <P><B>Note:</B> The setter methods (<code>setShort</code>, <code>setString</code>,
 * and so on) for setting IN parameter values
 * must specify types that are compatible with the defined SQL type of
 * the input parameter. For instance, if the IN parameter has SQL type
 * <code>INTEGER</code>, then the method <code>setInt</code> should be used.
 *
 * <p>If arbitrary parameter type conversions are required, the method
 * <code>setObject</code> should be used with a target SQL type.
 * <P>
 * In the following example of setting a parameter, <code>con</code> represents
 * an active connection:
 * <PRE>
 *   PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES
 *                                     SET SALARY = ? WHERE ID = ?");
 *   pstmt.setBigDecimal(1, 153833.00)
 *   pstmt.setInt(2, 110592)
 * </PRE>
 *
 * @see Connection#prepareStatement
 * @see ResultSet
 */

public interface PreparedStatement extends Statement {

所以,prepareStatement是可以表示预编译sql的statement,sql中可以事先设置参数,并且通过类型相对应的set方法设置进去并执行.

当然jdbc中的statement也好,preparestatement也好,都是一种规范,也就是一组规定好的接口,这边我们引入的是mysql的jdbc驱动,所以我们可以发现mysql驱动的对它的实现:

下面我们来对比一下使用原始的Driver和使用DriverManager分别获取连接操作数据库的方式

        com.mysql.jdbc.Driver driver = new Driver();//手动加载指定驱动
        //或者使用反射(取其一)
com.mysql.jdbc.Driver  driver = (Driver) (Class.forName("com.mysql.jdbc.Driver")).newInstance();

        String url = "jdbc:mysql://我是ip:3306/test?serverTimezone=PRC&useSSL=false";
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","我是密码");
        Connection con = driver.connect(url, info); //通过驱动获取数据库连接
        Statement statement = con.createStatement(); //创建sql执行器
        ResultSet resultSet = statement.executeQuery("select * from Course");//执行sql并返回结果
        ResultSetMetaData metaData = resultSet.getMetaData();
        while (resultSet.next()) {
            for (int i = 0; i < metaData.getColumnCount(); i++) {
                String value = resultSet.getString(i + 1);
                System.out.println(value);
            }
        }
Connection con = DriverManager.getConnection("jdbc:mysql://我是ip:3306/test?serverTimezone=PRC&useSSL=false","root","我是密码");//DriverManager会根据我们出入的参数自动帮我们找到适合的驱动并且返回连接
        Statement statement = con.createStatement();
        ResultSet resultSet = statement.executeQuery("select * from Course");
        ResultSetMetaData metaData = resultSet.getMetaData();
        while (resultSet.next()) {
            for (int i = 0; i < metaData.getColumnCount(); i++) {
                String value = resultSet.getString(i + 1);
                System.out.println(value);
            }
        }

通过上面的介绍,大家应该对原生jdbc有了深入的了解,正如大家所知道的一样,就是因为这样操作数据库是真的繁琐,所以后面一些列的持久层框架就应运而生;接下来我会和大家详细探讨mybatis这款经久不衰的orm框架.

2. myBatis框架

2.1 写在前面

     单独的mybatis框架原理是较为复杂的,如果从最基本开始聊起,我想就可以写一本书了,这篇文章我想和大家一起看看作为一个java后端程序员应该掌握的关于mybatis的原理性知识,在这里我不会和大家讨论如何使用mybatis或者mybatis-plus的使用,因为我觉得这样的博客文章,教学视频以及书籍实在是数不胜数,并且千篇一律,并且在我的理解来看,java后端程序员需要掌握的技术栈十分的繁杂,并且需要掌握的框架也是不胜枚举,但是说实话,这些框架其实都有一个共同点,那就是使用起来非常简单,容易上手,我想在看的各位小伙伴应该没有觉得在项目中使用mybatis框架很难,使用spring很难,使用springboot封装的redis很难,使用springboot封装的mq很难吧,因为这些框架本身就是那些技术大牛为了简化技术的使用而产生的,自然而然就会在使用上事半功倍,一气呵成.在这里我推荐大家可以参考各类技术栈的官方文档和寻找质量稍高的技术博客学习使用某类框架,当然如果技术接受能力很强可以直接根据框架本身的api和注释来上手(有点难度).所以这篇文章我只和大家讨论mybatis中的一些基本的使用原理,让我们在使用框架的同时可以知道为什么要这样写.

2.2 推荐文档和书籍

mybatis官方文档

mybatis-plus官方文档

mybatis原理探究类pdf文档 提取码:r85y

2.3 mybatis单独框架原理

1. mybatis中的重要概念介绍:
2. 详细解析

先来看看mybatis的整体原理图(来源于网络)
下面这张图片详细展示了mybatis中sql的执行和返回的过程

上面两张图是我觉得描述的较为详细的原理图,最下层是数据库,我们知道jdbc查询都是将得到的数据封装为resultset对象返回,mybatis是对jdbc的封装,并且是一个面向对象编程的框架,这里mybatis通过一些列的组件(ResultSetHandler)将resultset转化为java中的对象

所以介绍mybatis的基本原理我们可以从两个方向来看,一是sql的执行过程,二是sql执行后的返回封装的java对象的过程.,下面我们就一起来看

初始化过程会从下面开始

public class SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse()); //这一步就是封装加载配置信息,并根据Configuration对象返回SqlSessionFactory
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
------------------------------------------------------------------ 
protected final Configuration configuration;  
  
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  
------------------------------------------------------------------
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
  
-------------------------------------------------------------------

mybatis的前身为ibatis,当时sqlSession是这样进行操作数据库的

sqlSession = sessionFactory.openSession();
User user = sqlSession.selectOne("com.luo.dao.UserDao.getUserById", 1);

之后我们一般不使用上面那种,而是这样:

// 获得mapper接口的代理对象
PersonMapper pm = session.getMapper(PersonMapper.class);
// 直接调用接口的方法,查询id为1的Peson数据
Person p = pm.selectPersonById(1);
3. 代码调试
  1. 在pom中引入依赖:
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>
  1. mybatis配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- 打印查询语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>

    <!-- 和Spring整合后environment配置都会被干掉 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理,目前由mybatis来管理 -->
            <transactionManager type="JDBC" />
            <!-- 数据库连接池,目前由mybatis来管理 -->
            <dataSource type="POOLED"><!--有关于mysql数据库的各种信息-->
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://我是ip:3306/test?serverTimezone=PRC" />
                <property name="username" value="root" />
                <property name="password" value="我是密码" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!--将操作配置文件User.xml系添加进mapper-->
        <mapper resource="mybatis/Student.xml" />
    </mappers>
</configuration>
  1. Student实体类
public class Student {

    Integer id;
    String name;

    public Student(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

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

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  1. 编写mapper接口
public interface Studentmapper {

     Student findStudentById(int id);

     List<Student> findAllStudent();

     void deleteStudentById(int id);

}

不需要习惯性的写上@mapper注解,因为这边不是放在spring中管理,是我们自己手动编写程序加载mapper接口

  1. sql映射的xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.zuo.Studentmapper">

    <select id="findStudentById" parameterType="int" resultType="com.zuo.Student">
       select * from student where id = #{id}
    </select>
    <select id="findAllStudents"  resultType="com.zuo.Student">
        select * from  student
    </select>
    <delete id="deleteStudentById" parameterType="java.lang.Integer">
        delete from student where id = #{id}
    </delete>
</mapper>

namespace对应的当前的sql映射 xml配置文件对应的接口,然后配置好方法名和参数,返回值类型啥的,这些都是最基础的mybatis的使用,这里不再赘述,下面我们来debug更一遍下面这个测试程序

根据输入的文件名称路径返回一个inputStream
Resources.getResourceAsStream("mybatis-config.xml")
0==>

SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//前面实例化出来的SqlSessionFactoryBuilder是session工厂的建造者,但是它建造工厂需要使用"图纸",这个图纸就是封装好配置文件的Configuration对象,但是我们发现这里传入的参数Resources.getResourceAsStream("mybatis-config.xml")最终得到的是一个输入流呀,其实最终得到Configuration对象是在build方法内部,现在你看到的build方法只是一个操作外壳,真正的build方法在里面,传入的是解析封装得到的Configuration对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
        SqlSession sqlSession = sqlSessionFactory.openSession();
        Student stuTemp = (Student)sqlSession.selectOne("com.zuo.Studentmapper.findStudentById", 1);
        System.out.println(stuTemp);

1==>
//inputStream输入流参数已经连通了xml配置文件
public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
  
2==>

//inputStream输入流参数已经连通了xml配置文件
 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
    //实例化XMLConfigurationBuIlder,这个类中就包含了Configuration属性,还有一个XPathParser属性(后面详细介绍)  见3
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment,
      properties);
      //这里就是真实的build方法了,所以parser.parser()方法得到的肯定就是封装好的Configuration对象,其实类似的,这里
      return build(parser.parse());
      
      
3==>

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
//this中实例化了一个XPathParser作为参数,XPathParser的作用是真正用来解析xml的类,这里面传入了之前的输入流也就是连接到xml配置文件
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
  
4==>

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//这个父类的构造中是配置封装好mybatis中的通用的别名,包括各类数据类型别名
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;   //这里将实例化的XPathParser赋值给了XMLConfigBuilder中的属性了
  }
  
5==>

现在XMLConfigBuilder已经实例化出来了,XMLConfigBuilder在mybatis中是生成配置封装类Configuration的(但事实上真正解析xml配置文件为Configuration类的还是XPathParser,它只是外部的封装外壳),它里面包含Configuration配置信息(至此还只是封装好mybatis中的通用的别名,包括各类数据类型别名到Configuration类中),XPathParser(真正解析xml配置文件的类)

6==>
现在回到2,就来到下面的方法
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //主要看这里,从根节点开始解析,也就是<configuration>标签,可以看到XMLConfigBuIlder中真正解析配置文件的还是其中的XPathParser属性,它将xml节点解析出来,传给parseConfiguration方法,在这个方法中将信息封装到Configuration对象中  见7
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

7==>

然后一个个解析,通过XPathParser

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      //以这个方法为例,因为parseConfiguration传入的是configuration节点,也就是root节点,parseConfiguration就可以解析整个xml配置文件了, 下面这个方法解析的是properties节点,因为我们没有配置properties在xml中,进入这个方法里面(见下文)
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
  
  
  //context就是null,如果配置了就不是null,会将配置的xml的properties的键值存放到Configuration对象中的properties属性中
  private void propertiesElement(XNode context) throws Exception {
    if (context != null) { 
    //在xml节点中获取配置键值信息
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      //赋值
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }
  
 至此主配置文件就解析完成了,并且将所有的配置信息封装在了Configuration对象中返回了
 

8==>
再次回到2,现在可以建造工厂了,因为Configuration配置类已经OK了,也就相当于图纸有了,现在就可以build工厂了;

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
  
9==>
现在回到0,进入下面的方法
public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
并且最终打开一个sqlSession会进入下面得到方法,sqlSession对外提供mybatis的pai,内部封装了操作数据库的一些列信息,如事务,不同的sql执行器(如普通执行器.开启二级缓存的执行器)
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //生成sql执行器,这里传入的execType是需要生成的执行器的类型,这个类型是已经在我们的Configuration配置类中了,这个参数也就是从已经存在的封装好的Configuration配置类中取得的了,当然这个执行器的类型既然是封装在Config配置类中,那么就肯定是可以在外部自由配置的,就像这样
      <settings>
     <!--取值范围 SIMPLE, REUSE, BATCH -->
    	<setting name="defaultExecutorType" value="SIMPLE"/>
     </settings>
      
      final Executor executor = configuration.newExecutor(tx, execType);
      //最终实例化的是默认的sqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

10==>

//实例化默认的sqlSessionFactory,将所有的操作数据库的配置都赋值一下,这样就实例化完成了
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }




11==>接下来就是
这里的statement参数是我们传入进来的,
@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
    //因为Configuration配置类中有一个属性就是封装了mapper中的所有的MapperStatement,见下面图1-1
    //这里我们根据键取出这个相应的mappedStatement(前面已经介绍过了)
      MappedStatement ms = configuration.getMappedStatement(statement);
      //到这就是使用执行器执行了
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

12==>
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//从取出的这个当前需要使用的mapperedStatement中获取BoundSql
    BoundSql boundSql = ms.getBoundSql(parameterObject);
//这边是获取二级缓存的key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
//最终的sql查询方法
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  

13==>


@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //从当前使用的mappedStatement中获取查询二级缓存
    Cache cache = ms.getCache();
    //因为是第一次查询所以缓存为空
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //所以直接来到这个方法中
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  
14==>

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
      //直接来到数据库中查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
  

15==>

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
    //在这里是真的真的查询了
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
  
  
16==>
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  
17==>

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//现在出现了我们熟悉的预编译
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //最后通过resultSetHandler返回封装转换后的list类型
    return resultSetHandler.<E> handleResultSets(ps);
  }
  
之后就返回查询到的数据


图1-1

说到这,大家可以再回头看看那张架构原理图

由于篇幅较长,mybatis整合spring的原理我们将在下一篇文章中讲解,这里只介绍了

User user = sqlSession.selectOne("com.luo.dao.UserDao.getUserById", 1);

这样的方式,实际中常用的getMapper方法会在和spring整合的文章中详细解释哦

---未完待续,详细内容下篇更新持续更新spring-mybatis原理等 mybatis动态代理原理见上篇博客