SpringBoot与Mybatis中一条select语句的执行过程(上)

@Test  
void testMybatisFirstCache() {  
    List<code.entity.Test> select = testMapper.select(10L);  
}

MapperProxy.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);  
    }  
    return cachedInvoker(method).invoke(proxy, method, args, sqlSession);  
  } catch (Throwable t) {  
    throw ExceptionUtil.unwrapThrowable(t);  
  }  
}

判断是不是Object里面的方法,然后走对应的逻辑
MapperProxy.PlainMethodInvoker.invoke方法

private static class PlainMethodInvoker implements MapperMethodInvoker {  
  private final MapperMethod mapperMethod;  
  
  public PlainMethodInvoker(MapperMethod mapperMethod) {  
    this.mapperMethod = mapperMethod;  
  }  
  
  @Override  
  public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {  
    return mapperMethod.execute(sqlSession, args);  
  }  
}
  1. sqlSession是SqlSessionTemplate。executoryType是SIMPLE,枚举类型有三种,分别是SIMPLE(简单,默认),BATCH(批量)和REUSE(重用)
    ![[Pasted image 20240609141636.png]]

  2. args是10L
    MapperMethod.execute方法

public Object execute(SqlSession sqlSession, Object[] args) {  
	// 简化部分代码,由于本次只是select查询所以只看case SELECT部分代码
  Object result;  
  switch (command.getType()) {  
    case SELECT:  
      if (method.returnsVoid() && method.hasResultHandler()) {  
        executeWithResultHandler(sqlSession, args);  
        result = null;  
      } else if (method.returnsMany()) {  
        result = executeForMany(sqlSession, args);  
      } else if (method.returnsMap()) {  
        result = executeForMap(sqlSession, args);  
      } else if (method.returnsCursor()) {  
        result = executeForCursor(sqlSession, args);  
      } else {  
        Object param = method.convertArgsToSqlCommandParam(args);  
        result = sqlSession.selectOne(command.getName(), param);  
        if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) {  
          result = Optional.ofNullable(result);  
        }  
      }  
      break;  
  }  
  return result;  
}
  1. 判断sql执行类型,sql语句使用了select标签,就会被识别为select
<select id="select" resultType="code.entity.Test">  
    select * from test  
</select>
  1. 该查询返回值是List,所以命中method.returnsMany()判断
    MapperMethod.executeForMany方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {  
  List<E> result;  
  Object param = method.convertArgsToSqlCommandParam(args);  
  if (method.hasRowBounds()) {  
    RowBounds rowBounds = method.extractRowBounds(args);  
    result = sqlSession.selectList(command.getName(), param, rowBounds);  
  } else {  
    result = sqlSession.selectList(command.getName(), param);  
  }  
  // issue #510 Collections & arrays support  
  if (!method.getReturnType().isAssignableFrom(result.getClass())) {  
    if (method.getReturnType().isArray()) {  
      return convertToArray(result);  
    }  
    return convertToDeclaredCollection(sqlSession.getConfiguration(), result);  
  }  
  return result;  
}
  1. convertArgsToSqlCommandParam将args转换为map
    在这里插入图片描述

  2. 判断是否有RowBounds(分页相关参数),可以用以下写法来触发hasRowBounds判断

List<Test> select(@Param("id")Long id,RowBounds rowBounds);
  1. 由于demo中没有传RowBounds所以不会触发,会走selectList全量查询
  2. 在执行完查询后,对result进行处理并返回
    SqlSessionTemplate.SqlSessionInterceptor.invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,  
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);  
    try {  
      Object result = method.invoke(sqlSession, args);  
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {  
        // force commit even on non-dirty sessions because some databases require  
        // a commit/rollback before calling close()     
           sqlSession.commit(true);  
      }  
      return result;  
    } catch (Throwable t) {  
      Throwable unwrapped = unwrapThrowable(t);  
      if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {  
        // release the connection to avoid a deadlock if the translator is no loaded. See issue #22  
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);  
        sqlSession = null;  
        Throwable translated = SqlSessionTemplate.this.exceptionTranslator  
            .translateExceptionIfPossible((PersistenceException) unwrapped);  
        if (translated != null) {  
          unwrapped = translated;  
        }  
      }  
      throw unwrapped;  
    } finally {  
      if (sqlSession != null) {  
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);  
      }  
    }  
  }  
}
  1. 先获取sqlSession,如果有事务会保存在ThreadLocal里面重复使用,来保证事务的统一提交和回滚,如果没有则每次都会创建一个新的sqlSession
  2. 进入到try里面,执行完之后会判断当前是否处于事务中,如果没有则强制commit
  3. catch和finally里面也会有对应的会话判断操作,以及相应日志记录
    执行method.invoke方法进入到DefaultSqlSession.selectList
@Override  
public <E> List<E> selectList(String statement, Object parameter) {  
  return this.selectList(statement, parameter, RowBounds.DEFAULT);  
}

@Override  
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {  
  return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);  
}

private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {  
  try {  
    MappedStatement ms = configuration.getMappedStatement(statement);  
    dirty |= ms.isDirtySelect();  
    return executor.query(ms, wrapCollection(parameter), rowBounds, handler);  
  } catch (Exception e) {  
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);  
  } finally {  
    ErrorContext.instance().reset();  
  }  
}
  1. 在这里会给上RowBounds.DEFAULT,对应的是0-Integer.MAX_VALUE
  2. 继续往下给上Executor.NO_RESULT_HANDLER,是个null
  3. executor.query代表从sqlSession进入到executor了
  4. 此处的executor是CachingExecutor mybatis二级缓存相关处理都在这里
    CachingExecutor.query()
@Override  
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler)  
    throws SQLException {  
  BoundSql boundSql = ms.getBoundSql(parameterObject);  
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);  
  return 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);  
    if (ms.isUseCache() && resultHandler == null) {  
      ensureNoOutParams(ms, boundSql);  
      @SuppressWarnings("unchecked")  
      List<E> list = (List<E>) tcm.getObject(cache, key);  
      if (list == null) {  
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);  
        tcm.putObject(cache, key, list); // issue #578 and #116  
      }  
      return list;  
    }  
  }  
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);  
}
  1. createCacheKey生成缓存的key
  2. 在query方法里面首先判断二级缓存是否生效,如果没生效就走delegate.query方法
  3. delegate是SimpleExecutor,但query方法是BaseExecutor里面的方法,两者是父子关系
    SimpleExecutor(BaseExecutor).query
@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());  
  // 简化后代码,主要是为了关注查询时候的流程
  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--;  
  }  
  
  return list;  
}

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;  
}
  1. 首先从localCache里面加载,如果能找到就处理下参数输出,然后返回
  2. 如果找不到就执行queryFromDatabase从数据库里面查询
  3. 实际查询是doQuery方法,是个抽象方法由SimpleExecutor实现
  4. 查询成功后将数据put到缓存里面,缓存底层是个HashMap
    SimpleExecutor.doQuery
@Override  
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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,  
        boundSql);  
    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);  
  stmt = handler.prepare(connection, transaction.getTimeout());  
  handler.parameterize(stmt);  
  return stmt;  
}

@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);  
  }  
}
  1. newStatementHandler会去创建StatementHandler,RoutingStatementHandler里面有个delegate,对应的是实际StatemenHandler,在默认配置下是PreparedStatementHandler
    ![[Pasted image 20240609170559.png]]

  2. 进入prepareStatement方法第一步获取connection,第二步执行prepare方法,setStatementTimeout

  3. parameterize对参数进行处理,例如下面这个sql#{id}就是会被识别并处理

select * from test where id = #{id}
  1. parameterize核发就是下面这段代码
typeHandler.setParameter(ps, i + 1, value, jdbcType);
  1. handler.query(stmt, resultHandler)进入实际数据库查询

PreparedStatementHandler.query

@Override  
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
  PreparedStatement ps = (PreparedStatement) statement;  
  ps.execute();  
  return resultSetHandler.handleResultSets(ps);  
}
  1. PreparedStatement.execute就是jdbc层面执行数据库查询的操作
    1. resultSetHandler对结果集进行处理,有且仅有一个实现DefaultResultSetHandler
  2. 对于这块简而言之就是将结果集处理按ResultType或ResultMap处理成方法里面写的返回值,然后返回,暂不细看
  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值