开源框架-Mybatis系列

MyBatis源码阅读(二)----MyBatis 的初始化和数

2020-12-29  本文已影响0人  singleZhang2010

概述

前面我们已经熟悉了MyBatis,为了更清晰得追踪MyBatis执行过程,我们把项目简洁化,方便聚焦MyBatis核心代码。
另外在阅读源码前,应该已经熟悉官网的文档:

《MyBatis文档》https://mybatis.org/mybatis-3/zh/getting-started.html

一、在SpringBoot中运行MyBatis

  1. pom.xml文件中的依赖,简化后如下:
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
    </dependencies>
  1. 创建包:com.zhlab.mybatisdemo.chapter3,并创建实体类SysAdminUser
package com.zhlab.mybatisdemo.chapter3;

/**
 * @ClassName SysAdminUser
 * @Description //SysAdminUser
 * @Author singleZhang
 * @Email 405780096@qq.com
 * @Date 2020/12/29 0029 下午 4:35
 **/
public class SysAdminUser {

    private Integer adminUserId;
    private String userName;
    private String email;
    private String nickName;
    private Integer deptId;

    public SysAdminUser() {
    }

    // ....省略getter setter
}

  1. 创建接口类SysAdminUserMapper
package com.zhlab.mybatisdemo.chapter3;

import java.util.List;

public interface SysAdminUserMapper {
    List<SysAdminUser> getUserList(SysAdminUser user);
}
  1. 在resources目录下创建文件夹com/zhlab/mybatisdemo/chapter3,并创建映射文件SysAdminUserMapper.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.zhlab.mybatisdemo.chapter3.SysAdminUserMapper">
    <resultMap id="BaseResultMap" type="com.zhlab.mybatisdemo.chapter3.SysAdminUser">
        <id column="admin_user_id" jdbcType="INTEGER" property="adminUserId" />
        <result column="user_name" jdbcType="VARCHAR" property="userName" />
        <result column="nick_name" jdbcType="VARCHAR" property="nickName" />
        <result column="email" jdbcType="VARCHAR" property="email" />
        <result column="dept_id" jdbcType="INTEGER" property="deptId" />
    </resultMap>

    <select id="getUserList" resultMap="BaseResultMap">
        SELECT * FROM `sys_admin_user`
        <if test="deptId != null">
            WHERE dept_id = #{deptId}
        </if>
    </select>
</mapper>
  1. 在resources目录下,创建配置文件mybatis-config.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <package name="com.github.yeecode.mybatisdemo"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/demo?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

 <!-- 映射文件都添加到这里-->
    <mappers>
        <mapper resource="com/zhlab/mybatisdemo/chapter3/SysAdminUserMapper.xml"/>
    </mappers>
</configuration>
  1. 创建类Chapter3DemoApplication,如下
package com.zhlab.mybatisdemo.chapter3;

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.springframework.boot.autoconfigure.SpringBootApplication;

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

/**
 * @ClassName Chapter3DemoApplication
 * @Description //Chapter3DemoApplication
 * @Author singleZhang
 * @Email 405780096@qq.com
 * @Date 2020/12/29 0029 下午 4:34
 **/
@SpringBootApplication
public class Chapter3DemoApplication {

    public static void main (String[] args){
        //SpringApplication.run(Chapter3DemoApplication.class,args);

        // 第一阶段:MyBatis的初始化阶段
        String resource = "mybatis-config.xml";

        // 得到配置文件的输入流
        InputStream inputStream = null;
        try{
            inputStream = Resources.getResourceAsStream(resource);
        }catch (IOException e){
            e.printStackTrace();
        }

        // 得到SqlSessionFactory
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);

        // 第二阶段:数据读写阶段
        try(SqlSession session = sqlSessionFactory.openSession()){

            // 找到接口对应的实现
            SysAdminUserMapper userMapper = session.getMapper(SysAdminUserMapper.class);

            // 查询参数
            SysAdminUser userParam = new SysAdminUser();
            userParam.setDeptId(5);

            // 调用接口展开数据库操作
            List<SysAdminUser> userList = userMapper.getUserList(userParam);

            for (SysAdminUser user : userList) {
                System.out.println("name : " + user.getNickName() + " ;  email : " + user.getEmail());
            }
        }
    }
}
  1. 运行结果如下
运行结果

二、分析

从上述过程中可以看出,MyBatis的操作主要分为两大阶段:初始化阶段和数据库操作阶段。

2-1. MyBatis的初始化阶段

MyBatis的初始化在项目启动时进行,这里主要工作内容为:读取并解析配置文件、数据库连接等。

我们要知道每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。初始化阶段介绍创建SqlSessionFactory 实例的过程。

这里关注这几个类:
Resources、ClassLoaderWrapper(MyBatis的io包)
SqlSessionFactoryBuilder、Configuration(MyBatis的session包)
XMLConfigBuilder(MyBatis的builder包)

初始化阶段里的配置文件读取

 inputStream = Resources.getResourceAsStream(resource);

这段代码调用了Resource中的 getResourceAsStream方法,getResourceAsStream方法又调用了ClassLoaderWrapper的getResourceAsStream方法:

    InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
        ClassLoader[] var3 = classLoader;
        int var4 = classLoader.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            ClassLoader cl = var3[var5];
            if (null != cl) {
                InputStream returnValue = cl.getResourceAsStream(resource);
                if (null == returnValue) {
                    returnValue = cl.getResourceAsStream("/" + resource);
                }

                if (null != returnValue) {
                    return returnValue;
                }
            }
        }

        return null;
    }

※Resource类和 ClassLoaderWrapper类是负责读写外部文件。

SqlSessionFactory实例

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

可以看出SqlSessionFactory实例是通过SqlSessionFactoryBuilder的build方法来创建的,可以传入xml配置文件内容或者通过Configuration实例对象,源码如下:

    // 方法一:通过xml配置文件的InputStream对象 构建SqlSessionFactory 
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            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;
    }
  
    // 方法二:通过Configuration 对象 构建SqlSessionFactory 
    public SqlSessionFactory build(Configuration config) {
        //DefaultSqlSessionFactory
        return new DefaultSqlSessionFactory(config);
    }

我们的demo是采用了方法一,其实最终也是进入方法二里边,继续往下看。这里需要关注的核心代码是:

try {
    //步骤一
    //生成了一个 XMLConfigBuilder对象parser
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    //步骤二
    //调用了XMLConfigBuilder对象parser的 parse() 方法,返回值为Configuration对象
    //调用了方法二的build(Configuration config) 方法
    var5 = this.build(parser.parse());
}
//...当中省略
return var5;

继续跟踪XMLConfigBuilder对象parser的parse() 方法

    public Configuration parse() {
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            // 进入解析
            this.parsed = true;
            // "/configuration" 是整个配置文件的根节点
            //parseConfiguration具体解析各个节点,并往Configuration对象设值
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            //返回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);
        }
    }

parseConfiguration方法是具体解析配置文件里各个节点的(包括配置节点信息和映射文件信息),里边调用的各类方法都是往Configuration对象configuration里设值。

最终,Configuration对象会进入SqlSessionFactory的 build(Configuration config)方法,得到SqlSessionFactory对象。

所以,归纳一下初始化阶段,MyBatis做了以下几个操作:

2-2. MyBatis的数据操作阶段

从一阶段已经获取了SqlSessionFactory对象,那这个工厂就是会生产SqlSession对象的,数据库操作过程需要这个对象。

获得SqlSession

SqlSession session = sqlSessionFactory.openSession()

通过追踪代码可以看到SqlSession对象由DefaultSqlSessionFactory类的openSessionFromDataSource方法生成,源码如下:

    // DefaultSqlSessionFactory类中
    public SqlSession openSession() {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }

    /**
     * Configuration 配置信息
     * ExecutorType:执行器的类型。默认类型时simple
     * TransactionIsolationLevel:数据库隔离级别。
     * autoCommit:是否支持手动事务提交。
     */
    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);
            //调用Configuration对象,创建Executor
            //因为默认下Configuration里的cacheEnabled=true 
            //把Executor对象再传入CachingExecutor的构造函数,创建CachingExecutor对象
            //具体查看Configuration类中563行newExecutor方法
            Executor executor = this.configuration.newExecutor(tx, execType);
            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;
    }

    //Configuration类中563行newExecutor方法
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? this.defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Object executor;
        //根据类型创建Executor对象:BatchExecutor、ReuseExecutor、SimpleExecutor三个
        //它们都继承了BaseExecutor
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction);
        }

         //里边使用了装饰器模式,创建一个CachingExecutor对象
        //CachingExecutor为使用二级缓存的SQL执行器
        if (this.cacheEnabled) {
            executor = new CachingExecutor((Executor)executor);
        }

        Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
        return executor;
    }

在以上代码中可以看到,SqlSessionFactory里使用了Configuration对象,创建了Transaction对象、Executor对象等作为参数传入DefaultSqlSession构造函数,最后得到一个DefaultSqlSession对象。
进入DefaultSqlSession类可以看到各类增删改查等数据库操作方法,这里只是一个门面模式,具体交给Executor去执行,SqlSession对象可以供多次数据库读写操作复用不用多次创建。

映射接口文件(Mapper 接口类)与映射文件(Mapper xml文件)
demo中的代码片段如下:

      // 找到接口对应的实现
      SysAdminUserMapper userMapper = session.getMapper(SysAdminUserMapper.class);

跟踪代码,进入DefaultSqlSession类的getMapper方法,进而继续调用Configuration对象的getMapper方法,继续调用MapperRegistry对象的getMapper方法

    // DefaultSqlSession类
    public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }

    // Configuration类
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

     // MapperRegistry类
    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 {
                //通过MapperProxyFactory对象创建Class<T>类型的Mapper接口类实例
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

上面方法中可以看到使用了动态代理模式,使用MapperProxyFactory对象来创建各个传入的Mapper接口类的实例。
MapperProxyFactory类

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

这里返回的就是一个基于反射的动态代理对象,MapperProxy是一个自定义的实现了InvocationHandler接口的类,可以对其invoke方法进行监控,查看各种代理对象的信息比如名称、方法名、参数等等

    // MapperProxy类中
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (method.isDefault()) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        //获取MapperMethod 对象,传入method
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        
        //执行sql
        return mapperMethod.execute(this.sqlSession, args);
    }

    // 创建MapperMethod对象
    private MapperMethod cachedMapperMethod(Method method) {
        return (MapperMethod)this.methodCache.computeIfAbsent(method, (k) -> {
            return new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
        });
    }

上面的execute方法,所执行的就是demo里的这段

List<SysAdminUser> userList = userMapper.getUserList(userParam);

继续追踪,进入MapperMethod类,查看execute方法

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            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:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } 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());
        }

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

  //demo中我们是查询List这里关注executeForMany方法
    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        Object param = this.method.convertArgsToSqlCommandParam(args);
        List result;
        if (this.method.hasRowBounds()) {
            RowBounds rowBounds = this.method.extractRowBounds(args);
            result = sqlSession.selectList(this.command.getName(), param, rowBounds);
        } else {
            result = sqlSession.selectList(this.command.getName(), param);
        }

        if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
            return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        } else {
            return result;
        }
    }

这里最终调用了sqlSession对象的selectList方法。到这里MyBatis实现了Mapper接口的注入,并得到接口中的方法名,并进入数据库操作。

SQL操作
继续上述步骤,进入DefaultSqlSession类的selectList方法

    // DefaultSqlSession
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
            //获取MappedStatement对象,通过配置信息获取
            MappedStatement ms = this.configuration.getMappedStatement(statement);

            //执行executor对象里的query方法
            //这里的executor是在DefaultSqlSessionFactory中,通过Configuration对象创建的CachingExecutor
            //当然根据不同的配置,我们会有不同的Executor,后面具体分析会知道。
            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;
    }

接下来看CachingExecutor类中具体执行的代码

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);

        //  CacheKey为缓存键,它由createCacheKey方法创建
        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 = ms.getCache();
        if (cache != null) {
            this.flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, boundSql);
                List<E> list = (List)this.tcm.getObject(cache, key);
                //从缓存获取的结果为空的情况下,则通过delegate对象调用query方法,查询数据,并缓存数据
                if (list == null) {
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    this.tcm.putObject(cache, key, list);
                }

                return list;
            }
        }
        //不存在MappedStatement 缓存,则通过delegate对象调用query方法,查询数据
        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

this.delegate.query方法在BaseExecutor类中,this.delegate为构建CachingExecutor对象时传入的Executor对象,这里默认为SimpleExecutor,因为它类继承了BaseExecutor类,SimpleExecutor中没有query方法,所以query方法来自BaseExecutor类

//BaseExecutor类118行
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;
        }
    }

    //queryFromDatabase
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        
        // 这里是MyBatis的一级缓存,由PerpetualCache类型的成员对象维护,其底层还是一个HashMap。
        // 先在缓存中放置一个占位符
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

        List list;
        try {
            // 然后调用 doQuery
            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;
    }

doQuery方法在BaseExecutor中是个抽象方法,在SimpleExecutor类中有具体实现

    //SimpleExecutor类44行
    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();
            //通过Configuration对象的newStatementHandler方法
            //获取StatementHandler 对象,跟踪源码可以发现为PreparedStatementHandler
            //因为MappedStatement的Builder方法对statementType设值为StatementType.PREPARED
            //MappedStatement类181行
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

PreparedStatementHandler类中具体执行sql语句

    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        //转换为PreparedStatement类型,PreparedStatement类来自java.sql包
        PreparedStatement ps = (PreparedStatement)statement;
        //具体的execute方法由com.mysql.cj.jdbc包中的PreparedStatementWrapper类实现
        ps.execute();

        //查询结果交给 ResultHandler对象处理
        return this.resultSetHandler.handleResultSets(ps);
    }

PreparedStatementWrapper类中的execute方法

    public boolean execute() throws SQLException {
        try {
            if (this.wrappedStmt != null) {
                //可以看到查询完成之后的结果放在 PreparedStatement对象中
                return ((PreparedStatement)this.wrappedStmt).execute();
            } else {
                throw SQLError.createSQLException(Messages.getString("Statement.AlreadyClosed"), "S1000", this.exceptionInterceptor);
            }
        } catch (SQLException var2) {
            this.checkAndFireConnectionError(var2);
            return false;
        }
    }

好了,数据库查询操作过程可以归纳为以下几个步骤:

处理结果集
上面步骤中

 return this.resultSetHandler.handleResultSets(ps);

这里的handleResultSets方法来自接口ResultSetHandler类中

public interface ResultSetHandler {
    <E> List<E> handleResultSets(Statement var1) throws SQLException;

    <E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;

    void handleOutputParameters(CallableStatement var1) throws SQLException;
}

其具体实现由DefaultResultSetHandler类中的handleResultSets方法

public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
        List<Object> multipleResults = new ArrayList();
        int resultSetCount = 0;
        ResultSetWrapper rsw = this.getFirstResultSet(stmt);
        List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        this.validateResultMapsCount(rsw, resultMapCount);

        while(rsw != null && resultMapCount > resultSetCount) {
            ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
            this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
            rsw = this.getNextResultSet(stmt);
            this.cleanUpAfterHandlingResultSet();
            ++resultSetCount;
        }

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

        return this.collapseSingleResultList(multipleResults);
    }

这里具体做的就是把之前SQL查询出来的结果被遍历后放入了列表multipleResults 中并返回,即List<SysAdminUser>。
这里边的方法调用链路如下


DefaultResultSetHandler方法调用链路

在第二阶段的数据库操作中,MyBatis的工作内容如下

上一篇下一篇

猜你喜欢

热点阅读