Mybatis源码解析 第三章 查询SQl流程

55 阅读4分钟

MyBatis 查询SQl流程

MyBatis myBatis = mapper.getOne(1L);

  • 因为mapper对象是被MapperProxy类代理的实体类所以所有方法调用都会走到
    MapperProxy::invoke(此方法是MapperProxy继承InvocationHandler接口并重写的)

  • MapperProxy类中有个对象methodCache,是专门用来缓存方法的,每次请求
    MapperProxy对象都会尝试从缓存里面获取此方法,如果获取不到,就实例化此方法,然后缓存,再返回此方法的实例化
    private final Map<Method, MapperMethod> methodCache;

  • 每一个方法在MyBatis中就对应一个MapperMethod类,最后的SQl请求就是由此类的对象发起的,MapperMethod中有两个
    属性,分别是 SqlCommand command;和MethodSignature method;
    其中,method属性主要是方法的信息,比如方法返回值,方法传参等数据都是存放在这个对象里面
    command对象主要存放方法名,和SQL类型

invoke();方法

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
  if (Object.class.equals(method.getDeclaringClass())) {
    return method.invoke(this, args);
  } else if (isDefaultMethod(method)) {
    return invokeDefaultMethod(proxy, method, args);
  }
} catch (Throwable t) {
  throw ExceptionUtil.unwrapThrowable(t);
}
// 尝试从缓存中获取方法的缓存数据
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行SQL
return mapperMethod.execute(sqlSession, args);
}

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);
  }
}
  • 执行SQL mapperMethod.execute(sqlSession, args);mapperMethod就是MapperMethod类的实体类
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
            // 返回值是List
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
            // 返回值是Map
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
            // 返回值是指针(java的一个类Cursor.class)
          result = executeForCursor(sqlSession, args);
        } else {
            // 单对象返回
            // 参数映射(其他类型的请求,他们的参数映射都隐式得写到的对应的方法里) 经过映射后的 id=1 为:
            // {
            //     "id":1,
            //     "param1":1
            // }
          Object param = method.convertArgsToSqlCommandParam(args);
            // 执行SQL获取返回值
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      default:
        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;
  }
  • 参数映射 method.convertArgsToSqlCommandParam(args);
  参数映射(其他类型的请求,他们的参数映射都隐式得写到了对应的方法里)
  经过映射后的 id=1 为:
  {
      "id":1,
      "param1":1
  }  
  • 执行SQL sqlSession.selectOne(command.getName(), param);

默认的SQL执行类为DefaultSqlSession,此类继承了SqlSession接口,具有默认的请求方式

@Override
public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    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 {
        // 这里可以看到,mybatis查不到数据时,真的是返回null的
        return null;
    }
}
  • 查询列表SQL List list = this.selectList(statement, parameter);
  @Override
public<E> List<E> selectList(String statement,Object parameter,RowBounds rowBounds){
        try{
        // 从全局Configuration中获取解析xxMapper.xml得到的SQL对象,里面保存的是SQL模板,也就是请求要用到的SQL
        MappedStatement ms=configuration.getMappedStatement(statement);
        // 通过SQL执行器去执行SQL
        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();
        }
        }
  • MyBatis 三种执行器类型 ExecutorType
  • SIMPLE
    > SimpleExecutor是一种常规执行器,每次执行都会创建一个statement,用完后关闭。
  • REUSE
    > ReuseExecutor是可重用执行器,将statement存入map中,操作map中的statement而不会重复创建statement。
  • BATCH
    > BatchExecutor是批处理型执行器,doUpdate预处理存储过程或批处理操作,doQuery提交并执行过程。
  • 查询SQL executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

默认会进入CachingExecutor(缓存执行器)类中,此类也继承了执行器接口

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取绑定SQL 即:select XXX from XXX where XXX
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 根据请求的SQL和请求数据等信息,获取一个唯一的缓存key键
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 执行查询
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  • 执行SQL query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    @Override
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){
        // 如果设置了清空缓存,则清空缓存
        flushCacheIfRequired(ms);
        // 如果SQL类中设置的是使用缓存,并且返回值处理器为null
        if(ms.isUseCache()&&resultHandler==null){
        // 检查是否有带有OUT参数的储存过程(这里不是很理解)
        ensureNoOutParams(ms,parameterObject,boundSql);
@SuppressWarnings("unchecked")
// 从缓存中获取缓存数据
                List<E> list=(List<E>)tcm.getObject(cache,key);
        if(list==null){
        // 缓存数据为空,执行SQL查询
        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);
        }
  • 执行SQL delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    @SuppressWarnings("unchecked")
@Override
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{
        // 从数据库中查询,再往后走就需要用到对JDBC支持的类了,alibaba的druid连接池就有此类接口
        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;
        }
  • 返回值解析