花里胡哨系列之Mybatis源码---SQL执行流程(四)

发布时间:2022-08-19 12:46

一.本章概述

    对于Mybatis的配置文件,映射文件在之前的博客已经分析过,之后正式进入Sql执行分析阶段。Sql执行整体过程还是比较清晰的,但是其中有很多细节值得琢磨。

    Sql执行过程涉及到的模块:

    1.SqlSession获取

    2.参数解析

    3.缓存机制

    4.连接获取

    5.结果映射

    这章计划先把sql执行流程串起来,有个基本的执行印象,然后再各个击破,抽丝剥茧分析模块细节。

二.基本用法

   在获取到 SqlSessionFactory后,可以使用以下代码执行sql

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}

   或者

try (SqlSession session = sqlSessionFactory.openSession()) {
  Blog blog = session.selectOne("org.apache.ibatis.domain.blog.mappers.BlogMapper.selectBlogWithPostsUsingSubSelect", 1);
}

三.sql流程分析

  1.获取SqlSession

    sqlSessionFactory.openSession()

DefaultSqlSessionFactory:
public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    //创建事务管理器
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //创建执行器
    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:
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 {//默认类型
    executor = new SimpleExecutor(this, transaction);
  }
  //是否开启二级缓存,包装类装饰
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  //插件
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

 2. SqlSession 已经得到了,看看这行代码: BlogMapper mapper = session.getMapper(BlogMapper.class)

DefaultSqlSession
public  T getMapper(Class type) {
  //在这一步会根据type接口创建type的代理对象
  return configuration.getMapper(type, this);
}
Configuration:
public  T getMapper(Class type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry:
public  T getMapper(Class type, SqlSession sqlSession) {
  //映射文件接口绑定时,已经将 接口保存到 knownMappers中
  final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
  if (mapperProxyFactory == null) {//未绑定,抛异常
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    //创建代理对象
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}
MapperProxyFactory:
public T newInstance(SqlSession sqlSession) {
  final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy mapperProxy) {
  //jdk动态代理
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

     通过jdk动态代理方式,得到的 接口是一个代理对象,即MapperProxy,看看这个对象的 invoke方法

MapperProxy:
@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 (method.isDefault()) {
      if (privateLookupInMethod == null) {//jdk1.8 接口默认方法
        return invokeDefaultMethodJava8(proxy, method, args);
      } else {
        return invokeDefaultMethodJava9(proxy, method, args);
      }
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  // 从缓存中获取 MapperMethod 对象,若缓存未命中,则创建 MapperMethod 对象
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  // 调用 execute 方法执行 SQL
  return mapperMethod.execute(sqlSession, args);
}

MapperMethod :

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT:
      if (method.returnsVoid() && method.hasResultHandler()) {
        // 如果方法返回值为 void,但参数列表中包含 ResultHandler,表明
        // 使用者想通过 ResultHandler 的方式获取查询结果,而非通过返回值
        // 获取结果
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        // 执行查询操作,并返回多个结果
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        // 执行查询操作,并将结果封装在 Map 中返回
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {
        // 执行查询操作,并返回一个 Cursor 对象
        result = executeForCursor(sqlSession, args);
      } else {
        // 执行查询操作,并返回一个结果
        // 获取参数名对应的参数值
        Object param = method.convertArgsToSqlCommandParam(args);
        //调用 SqlSession的方法
        result = sqlSession.selectOne(command.getName(), param);
        if (method.returnsOptional()
            && (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      break;
    case FLUSH:
      // 执行刷新操作
      result = sqlSession.flushStatements();
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  // 如果方法的返回值为基本类型,而返回值却为 null,此种情况下应抛出异常
  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;
}

这里看看SqlSession的selectOne方法:

public  T selectOne(String statement, Object parameter) {
  // Popular vote was to return null on 0 results and throw exception on too many.
  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;
  }
}
@Override
public  List selectList(String statement, Object parameter) {
  return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

@Override
public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    //statement就是id,即dao层接口方法:全限定类名+方法名
    MappedStatement ms = configuration.getMappedStatement(statement);
    //准备执行
    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();
  }
}

3.   Blog blog = session.selectOne("org.apache.ibatis.domain.blog.mappers.BlogMapper.selectBlogWithPostsUsingSubSelect", 1);

     直接调用SqlSession的selectOne方法,传入MappedStatement的id,参数,和 session.getMapper() 殊途同归。

DefaultSqlSession:
@Override
public  T selectOne(String statement, Object parameter) {
  // Popular vote was to return null on 0 results and throw exception on too many.
  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;
  }
}
@Override
public  List selectList(String statement, Object parameter) {
  return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

@Override
public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    //statement就是id,即dao层接口方法:全限定类名+方法名
    MappedStatement ms = configuration.getMappedStatement(statement);
    //准备执行
    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();
  }
}

4.Executor执行sql操作

   执行器的流程如下:

 花里胡哨系列之Mybatis源码---SQL执行流程(四)_第1张图片

   以查询为例,分析Executor执行流程:

BaseExecutor:
@Override
public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  // 根据入参,解析动态sql语句
  BoundSql boundSql = ms.getBoundSql(parameter);
  //创建缓存key
  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
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 (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List list;
  try {
    queryStack++;
    //从一级缓存中获取缓存项,从这里可以看出 mybatis无法关闭一级缓存。。
    list = resultHandler == null ? (List) 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();
    }
    // issue #601
    deferredLoads.clear();
    /**
     * 一级缓存存在的弊端,在集群节点中,如果两台服务器上查询同一语句,此时缓存内容相同
     * 如果一台执行更新,重新查询后,会将更新后的数据存入缓存,此时两条服务器中的一级缓存
     * 内容不一致
     *
     * 通过设置 
     *   ,默认session
     * 
     * 本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。
     *
     */
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482,清空缓存,此时一级缓存失效。。
      clearLocalCache();
    }
  }
  return list;
}
private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List 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:
@Override
public  List 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.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  //获取数据库连接,和数据源类型相关
  Connection connection = getConnection(statementLog);
  //创建statement
  stmt = handler.prepare(connection, transaction.getTimeout());
  //设置参数值,将 ? 替换成真正的 参数
  handler.parameterize(stmt);
  return stmt;
}
PreparedStatementHandler:
@Override
public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  //执行sql
  ps.execute();
  //处理结果
  return resultSetHandler.handleResultSets(ps);
}

四.总结

     sql执行流程已经分析完了,但很多细节需要继续完善;下一节将分析 参数解析,看看Mybatis如何将接口传入的参数解析到sql语句中。

     最后,来一张sql执行流程图:

 花里胡哨系列之Mybatis源码---SQL执行流程(四)_第2张图片

    结语:我反对这门亲事!

 

 

 

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号