mybatis源码学习之执行过程分析(4)——映射文件中sql的获取和sql语句的执行

mybatis源码学习及分析之执行过程分析(4)——映射文件中sql的获取和sql语句的执行

在上一篇中分析了XXXMapper接口的获取,其实质上拿到的是一个MapperProxy。这一篇主要分析sql的执行。

mybatis源码学习之执行过程分析(4)——映射文件中sql的获取和sql语句的执行_第1张图片

MapperProxy.java

  private final SqlSession sqlSession;
  //接口名字记录在这里
  private final Class mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if(Object.class.equals(method.getDeclaringClass())) {
        try {
            return method.invoke(this, args);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    } else {
        MapperMethod mapperMethod = this.cachedMapperMethod(method); // 这里获取到了我们在Mapper接口中定义的方法;cachedMapperMethod()重点在分析,见分隔符
        //args 即传入的参数
        return mapperMethod.execute(this.sqlSession, args);
    }
}

可以看到MapperProxy实现了InvocationHandler接口。
当我们调用List users = userMapper.findAll();时,就触发了代理的执行。//更正,为了看到参数的解析,后面使用User user = userMapper.findById(1).。

mybatis源码学习之执行过程分析(4)——映射文件中sql的获取和sql语句的执行_第2张图片

cachedMapperMethod(Method method)方法中可以看到,首先回去缓存中拿这个方法,没有则新建MapperMethod并放入缓存。

MapperMethod中保存着sqlCommand的类型[SELECT在后面执行sql时会用到]与调用方法名findAll。

到这里为止,我们拿到了MapperPrxoy代理和MapperMethod,就知道需要执行的对应mapper.xml文件中的对应的sql语句了。

=================== 欢乐的分隔符 ===========================

重点来看一下cachedMapperMethod(Method method)方法。

MapperProxy.java

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
       //重点在这里
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

来看MapperMethod的构造方法

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

mybatis源码学习之执行过程分析(4)——映射文件中sql的获取和sql语句的执行_第3张图片
如上图所示,在MapperMethod有3个静态类ParamMap、SqlCommand和MethodSignature。

(1)SqlCommand中String name保存的是我们调用的方法,SqlCommandType保存sql语句的类型。如图:
这里写图片描述

(2)MethodSignature中保存着方法调用的返回值等信息。
mybatis源码学习之执行过程分析(4)——映射文件中sql的获取和sql语句的执行_第4张图片

(3)ParamMap保存着参数信息

=============== 分隔符结束啦 ====================

接下来到mapperMethod.execute(this.sqlSession, args)的执行。

MapperMethod.java

public Object execute(SqlSession sqlSession, Object[] args) {
    Object param;
    Object result;
    switch(MapperMethod.SyntheticClass_1.$SwitchMap$org$apache$ibatis$mapping$SqlCommandType[this.command.getType().ordinal()]) {
    case 1:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
        break;
    case 2:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
        break;
    case 3:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
        break;
    case 4:
        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);
        }
        break;
    case 5:
        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;
    }
}

在这里,根据判断选择执行的类型,本例中调用了
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);,其实最终都调用了DefaultSqlSession接口中的对应的方法。

DefaultSqlSession.java

public  T selectOne(String statement, Object parameter) {
    //实际上调用了selectList
    List list = this.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;
    }
}

 public  List selectList(String statement, Object parameter) {
 //使用默认的分页参数
     return this.selectList(statement, parameter, RowBounds.DEFAULT);
 }

//RowBounds主要用来分页。
public RowBounds() {
    this.offset = 0;
    this.limit = 2147483647;
}

//最终调用
 public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
     List var5;
     try {
         //重点:将Mapper接口和Mapper.xml文件映射关系对应起来
         MappedStatement e = this.configuration.getMappedStatement(statement);
         //调用Executor的query去执行sql,下一篇分析
         var5 = this.executor.query(e, 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;
 }

首先是获取了Configuration中的 Map mappedStatements = new StrictMap("Mapped Statements collection");,然后通过statement作为key获取MappedStatement。

mybatis源码学习之执行过程分析(4)——映射文件中sql的获取和sql语句的执行_第5张图片

获取到的MappedStatement如下图:

mybatis源码学习之执行过程分析(4)——映射文件中sql的获取和sql语句的执行_第6张图片

============= sql 的执行 =================

下面分析这句代码的执行:

executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

首先来看Executor接口,

mybatis源码学习之执行过程分析(4)——映射文件中sql的获取和sql语句的执行_第7张图片

上一篇我们断电跟踪到了this.executor.query(e, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER)。调用了CachingExecutor中的public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
ResultHandler resultHandler)

CachingExecutor.java

public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, 
        ResultHandler resultHandler) throws SQLException {

    //拼接Sql,在这里将我们xml中定义的sql语句与传入的参数进行拼接
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
CachingExecutor.java

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, parameterObject, boundSql);
            List 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;
        }
    }
    //缓存中没有数据时,直接委托给Executor去执行sql的查询操作
    return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
BaseExecutor.java

public  List 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 {
                //调用queryFromDatabase
                list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            --this.queryStack;
        }

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

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

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

        return list;
    }
}
BaseExecutor.java

private  List 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中的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;
}
SimpleExecutor.java

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

可以看到这里使用JDBC的Statement。
mybatis源码学习之执行过程分析(4)——映射文件中sql的获取和sql语句的执行_第8张图片

再来看stmt = this.prepareStatement(handler, ms.getStatementLog());

SimpleExecutor.java

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //从dataSource中获取connection
    Connection connection = getConnection(statementLog);
    //准备Statement 
    stmt = handler.prepare(connection, transaction.getTimeout());

    handler.parameterize(stmt);
    return stmt;
  }
RoutingStatementHandler.java


  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }
BaseStatementHandler.java
//这里同样用到了模板方法模式
  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {

      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }
PreparedStatementHandler.java

  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
      //获取预编译的sql语句
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
    //调用
      return connection.prepareStatement(sql);
    }
  }

至此stmt = handler.prepare(connection, transaction.getTimeout());的调用结束,返回含有预编译sql语句的Statement。

那么接下来就是将预编译的sql编译成完整的sql语句了。
handler.parameterize(stmt);方法正是为了设置参数。

DefaultParameterHandler.java

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

调用栈如下:
mybatis源码学习之执行过程分析(4)——映射文件中sql的获取和sql语句的执行_第9张图片

在DefaultParameterHandler中对PrepareStatemet中的?做了替换,使得stat中的sql语句变得完整。

Now,我们有了包含完整sql语句的Statement了。使用过JDBC的话,我们都知道现在可以调用stat.execute()。在这里Mybatis使用MappedStatement#query(Statement statement, ResultHandler resultHandler)去执行sql。

PreparedStatementHandler.java

public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement)statement;
    ps.execute();
    return this.resultSetHandler.handleResultSets(ps);
}

实际上使用了JDBC的PreparedStatement 执行了Sql语句。并将结果返回。

这一篇中核心的StateMentHandler、MapperProxy等相关类,后面专门写一篇分析。
接下来就是返回结果的处理了,具体代码在下一篇中分析。

你可能感兴趣的:(框架学习,mybatis源码)