• mybatis源码分析(五)------------SQL的执行过程


    在对SQL的执行过程进行分析前,先看下测试demo:

    /**
     * @author chenyk
     * @date 2018年8月20日
     */
    
    public class GoodsDaoTest {
        
        private static SqlSessionFactory sqlSessionFactory = null;
    
        @Test
        public void selectGoodsTest(){
            SqlSession sqlSession = getSqlSessionFactory().openSession(true);  // 1.加载mybatis配置文件  2.加载Mapper映射文件
            GoodsDao goodsMapper = sqlSession.getMapper(GoodsDao.class);    // 3.使用JDK动态代理的方式生成Mapper的代理对象
            goodsMapper.selectGoodsById("1"); // 4.SQL的执行过程
            sqlSession.commit();    
        }
        
        public static SqlSessionFactory getSqlSessionFactory() {
            String resource = "spring-ibatis.xml";
            if(sqlSessionFactory == null){
                try {
                    sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources
                            .getResourceAsReader(resource));
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            return sqlSessionFactory;
        }
    
    }

    整个的流程就是上面注释部分所写,加载配置文件------》加载映射文件-------》为Mapper接口生成代理对象----------》调用方法时,真正的执行逻辑是在invoke方法中。这篇文章就从MapperMethod类的execute方法入手,来分析SQL的执行过程。对于前面三个过程在其他文章中已经做了分析。好了,直接进入execute方法:

     public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
    // insert操作
    if (SqlCommandType.INSERT == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); } else if (SqlCommandType.UPDATE == command.getType()) {
    // update操作 Object param
    = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); } else if (SqlCommandType.DELETE == command.getType()) {
    // delete操作 Object param
    = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); } else if (SqlCommandType.SELECT == command.getType()) {
    // select操作:返回类型为void,同时使用了ResultHandler
    if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) {
    // select操作:返回多条记录,集合或者数组 result
    = executeForMany(sqlSession, args); } else if (method.returnsMap()) {
    // select操作:返回Map结构 result
    = executeForMap(sqlSession, args); } else {
    // select操作:返回一条记录,其实还是使用了selectList Object param
    = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } } else { throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }

    1.查询

    我们先从查询操作分析,对于executeWithResultHandler,executeForMany,executeForMap这些方法,都是调用了对应sqlSession.select*的方法,sqlSession提供了各种不同的select方法。比如executeForMany,底层调用的是sqlSession.selectList方法,如果返回的结果类型是LIst,那么直接返回就可以了,如果返回的是数组或者set,那么在executeForMany方法中,会对List进行包装,然后返回。可以看下源码:

     private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    // 查询的结果类型是List List
    <E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) {
    // 如果有分页 RowBounds rowBounds
    = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); } // issue #510 Collections & arrays support
    // 如果方法中定义的返回类型不是List,再进行判断 if (!method.getReturnType().isAssignableFrom(result.getClass())) {
    // 如果方法中定义的返回类型是数组,那么对查询的结果List转为数组
    if (method.getReturnType().isArray()) { return convertToArray(result); } else {
    // 否则,按照方法中定义的返回类型,将List进行转换
    return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; }

    对于查询操作,其原理都大同小异,所以我们将以其中一个的select操作进行分析整个过程,然后再针对整个过程中的涉及到重要节点进行分析。可以这样理解:一棵大树,我们从底部到顶部对主干先大致分析一遍,然后再对主干上的比较粗的枝干进行分析。接下里,就对selectOne这个主干进行分析。

    1.1 selectOne方法分析

      public <T> T selectOne(String statement, Object parameter) {
        // Popular vote was to return null on 0 results and throw exception on too many.
    // 还是调用的selectList方法,只是返回第一条记录 List<T> list = this.<T>selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) {
    // 如果查询出的数据有多条,则会返回报错信息
    throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } }

    进入selectList方法:

      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
    //获取MappedStatement MappedStatement ms
    = configuration.getMappedStatement(statement);
    // 调用Executor实现类中的query方法 List
    <E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); return result; } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }

    这里有个问题值得思考:此处的query方法是调用Executor的哪个实现类的呢?默认是调用CachingExecutor的query方法,这个可以从sqlSessionFactory.openSession这个过程可以看出。CachingExecutor是个装饰类,用于给目标类增加二级缓存功能,那么目标类就是SimpleExecutor。关于Executor这一块在后面会做讲解。

    进入CachingExecutor中的query方法:

     public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取BoundSql BoundSql boundSql
    = ms.getBoundSql(parameterObject); // 创建CacheKey
    CacheKey key
    = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 进入此方法
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
          throws SQLException {
    // 从MappedStatement中获取缓存,如果映射文件中没有配置缓存,则此处cache==null Cache cache
    = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) {
    // 二级缓存未命中,则调用被装饰类的query方法:SimpleExecutor的query方法,它继承了BaseExecutor 进入此方法 list
    = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    // 将查询结果放到缓存中 tcm.putObject(cache, key, list);
    // issue #578. Query must be not synchronized to prevent deadlocks } return list; } }
    // 进入此方法
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

    由于SimpleExecutor继承了BaseExecutor,并没有对query方法重新,所以进入BaseExecutor的query方法:

     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(); } deferredLoads.clear(); // issue #601 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { clearLocalCache(); // issue #482 } } return list; }

    上面这个方法主要是先从一级缓存中获取数据,如果缓存未命中,则再查询数据库,接下来进入queryFromDatabase方法:

      private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
    // 向缓存中存储一个占位符 localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
    // 数据库查询操作 list
    = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally {
    // 移除占位符 localCache.removeObject(key); }
    // 缓存查询的结果 localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }

    然后我们进入SimpleExecutor的doQuery方法,进行真正的数据库查询操作:

      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 StatementHandler handler
    = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 创建statement
    stmt
    = prepareStatement(handler, ms.getStatementLog());
    // 查询操作
    return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }

    接下来,进入PreparedStatementHandler类的query方法:

      public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
    // 执行SQL ps.execute();
    // 处理查询结果
    return resultSetHandler.<E> handleResultSets(ps); }

    以上就是mybatis查询的主要过程,主干部分理清楚了,接下来就要对主干上的重要的枝干部分进行一个个的分析。

    1.2 Executor解析

    我们应该还记得上面提到的Executor接口的实现类是指CachingExecutor这个装饰类,它装饰的目标类是SimpleExecutor,我们从何处得知呢?首先进入DefaultSqlSession,Executor接口是它的一个属性,在DefaultSqlSession构造函数中给Executor赋予了值,所以搞清楚在DefaultSqlSession实例化构造对象时,传入的Executor类型就可以了。进入DefaultSqlSessionFactory的openSession方法:

     public SqlSession openSession() {
    // 进入此方法
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }
      private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
    // 获取environment
    final Environment environment = configuration.getEnvironment();
    // 根据environment获取transactionFactory
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    // 获取transaction对象 tx
    = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    // 实例化一个Executor对象,此处的execType默认是SIMPLE 进入此方法
    final Executor executor = configuration.newExecutor(tx, execType); 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(); } }

    进入configuration.newExecutor方法,重点部分在这:

      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
          executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
          executor = new ReuseExecutor(this, transaction);
        } else {
    // 由于executorType==SIMPLE executor
    = new SimpleExecutor(this, transaction); }
    // 如果映射文件配置了二级缓存,则对SimpleExecutor进行装饰,而装饰类是CacheExecutor,这就是创建DefaultSqlSession时传入的Executor参数
    if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }

    好了,现在对于Executor的实现类为什么是CachingExecutor,已经清楚了。对于Executor的介绍可以看下这篇文章:《Executor介绍》

     未完待续。。。

  • 相关阅读:
    客户端与服务器持续同步解析(轮询,comet,WebSocket)
    JQuery批量上传插件Uploadify使用详解及参数说明
    win7下安装配置tomcat,java运行环境
    我在Facebook工作的十大经验分享
    jquery submit方法在IE6下的诡异bug
    小练一下canvas版简单时钟与css3版漂亮时钟
    全面解析jquery实现回车键提交表单
    IE8页面加载速度测试
    微软发布Visual Studio 2008 SP1
    开发人员为什么要支持非IE浏览器的四个故事
  • 原文地址:https://www.cnblogs.com/51life/p/9628906.html
Copyright © 2020-2023  润新知