Mybatis源码解析——执行流程

Mybatis源码解析——执行流程

​ 吃了一年鱼,不知道是什么味道。这几天心血来潮手撸mybatis源码。

​ mybatis作为一个优秀的数据库框架,将sql语句等与java代码解耦,只要进行简单的配置,就可以对数据库进行操作。至于怎么配置,在我的其他篇博客有介绍,这里主要讲解执行流程。

demo

一、核心对象创建

//就是一个简单的配置文件读取
SqlSessionFactoryBuilder facbd = new SqlSessionFactoryBuilder();
SqlSessionFactory fac=facbd.build(Resources.getResourceAsStream("xml"));
SqlSession sqlsesssion = fac.openSession();

//sqlsession源码简化,想详细看搜方法名
//由下可以看出,就只是将配置文件通过XML解析后变为Java类,也就是Configuration核心对象。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
 SqlSessionFactory var5;
var5 = this.build(parser.parse());  //调用本类的build方法
 return va5
}
public SqlSessionFactory build(Configuration config) {
     return new DefaultSqlSessionFactory(config);
}

二、创建SqlSession对象,执行CRUD操作

​ sqlsession操作并不像标题那么简单,是相当的复杂!首先说明,不同的配置会产生的不同的核心对象,这里我所介绍的是开启二级缓存后的执行流程,因为涵盖的比较多,经此而已。

​ 在讲解之前先介绍核心对象相关的几个重要对象。

  1. TransactionFactory

    事务工厂对象,通过此对象可创建Transaction对象,Transaction这里使用的实现是JdbcTransaction,这个对象实际是只是注入了DataSource与当前使用的Connection对象,底层实际也是通过这个对象获取Connection连接对像,再通过此对象对事务进行提交!

  2. Excutor

    Excutor接口相比就麻烦多了,此接口有两个直接子类CachingExecutor和BaseExecutor,BaseExecutor是一个抽象类,他的实现类有着更加偏底层的代码。通过核心对象可以创建Excutor的实现类对象。开启二级缓存后创建的Excutor,返回的是CachingExcutor,内部注入BaseExcutor的实现类,对其进行了增强,也就是一些添加缓存记录的操作。没有配置缓存,就直接是BaseExecutor的实现类。这个对象是通过核心对象创建,注入到SQL Session的对象中。

  3. TransactionManager

    处理缓存的数据的对象,通过TransactionalCache将sql查询的结果,再次通过Cache的实现类进行操纵。自定义的二级缓存就是通过实现Cache接口,核心配置类配置,才会提交到redis或内部的HashMap中。通过核心对象传入参数到TransactionFactory工厂创建事务管理器,通过此对象对mybatis框架的事务进行提交。(Spring中已删除,使用Spirng事务管理平台)

  4. MappedStatement

    我们在使用mybatis时候,总是要写mapper文件,mapper文件每一个sql语句标签(指select、update等标签)都会生成MappedStatement对象。此对象也有一个Cache属性,用来存储缓存的结果。详细了解可以看我的mybatis二级缓存理解。这个对象主要就是获取sql语句了。

sql语句主要可以分为四种,这里我只选其一,那就是select语句了.

demo= sqlSession.selectOne("statementId");

​ selectOne方法也是一层层的方法调用,最终调用此类的selectList方法调用注入的Excutor实现类CachingExecutor,使用的是query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler)方法,通过一些方法生成CacheKey,作为二级缓存(Map)的键,提高SqlSession多次相同的sql语句查询效率。

以下是通过CachingExecutor最终调用的方法。

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        Cache cache = ms.getCache();
        if (cache != null) {
            this.flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, boundSql);
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                    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);
    }

从上的方法中,可以看出CachingExcutor的执行流程大致先查看缓存中有没有数据,有数据,就直接从缓存中拿,这里是通过上面TransactionManager对象获取缓存;如过没有数据,再通过代理的Excutor(实质执行者,为BaseExcutor的实现类)执行query。

在BaseExcutor中实际调用的下面的方法。

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

由上面看出,也是与CachingExcutor雷同,内部也是封装了Excutor的子类。执行过程也是查看一级缓存中有没有缓存的结果,有就直接用;没有则从数据库拿。

下面也就是最后的调用了。

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

// 通过TransactionManager获取连接对象,最后生成Statement返回。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Connection connection = this.getConnection(statementLog);
        Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
    }

上面的方法相信学过jdbc的应该不陌生了,通过Statement执行sql语句逐级放回结果了。

原文地址:https://www.cnblogs.com/theStone/p/14452163.html