MyBatis 源码分析-03 - MyBatis 方法执行流程分析

img

限于篇幅, 本文仅分析 mapper.selectBlog(1)这一过程,不包括 mapperproxy 代理对象生成, 以及方法参数解析, 以及resultset 解析的过程.

ps: 图片来自聊聊MyBatis 缓存这件事

Version :

  • MyBatis : 3.5.5

  • 项目地址 : MyBatis-source-learn

  • PS: 若文章字体偏大或者偏小,建议通过 ctrl键+鼠标滑轮 进行修改,以提升阅读效果.(带来不便,请谅解!)

流程:

userMapper.selectBlog(1);
	MapperProxy.invoke(); 
		MapperMethod.execute()
            DefaultSqlSession.selectOne();
				DefaultSqlSession.SelectList(); 
					cachingExecutor.query(); // 二级缓存 CachingExecutor.Cache
						baseExecutor.query();// 一级缓存 SimpleExecutor.localCache(Map)
							baseExecutor.queryFromDataBase();
								simpleExecutor.doQuery();

测试代码:

ps :其他代码请自己去 MyBatis-source-learn查看

测试类:

  • CreateSqlSession.java
public class CreateSqlSession {

    public static void main(String[] args) {
//         1. 连接数据库,配置基本参数
        DataSource dataSource = getDataSource();
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        Environment environment = new Environment("development",  transactionFactory, dataSource);
         Configuration configuration = new Configuration(environment);
//         2.  处理XXXMapper, 生成SqlSource(即处理xml 以及注解生成的SQL语句 与PreparedStatement绑定)
        configuration.addMapper(BlogMapper.class);
        // 3.  获取SqlSession
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        SqlSession sqlSession = sqlSessionFactory.openSession();
//         4. 获取mapper,执行方法
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        for (int i = 0; i < 10; i++) {
            Blog blog = mapper.selectBlog(1);
            System.out.println("i: "+blog);
        }
    }

只分析 mapper.selectBlog(1)这个方法执行流程, 必要内容会给予补充说明.

**PS: MyBatis 默认查询 顺序是 二级缓存 (CachedExecutor.cache) -> 一级缓存(BaseExecutor.localCache) -> 数据库. **

源码分析:

MapperProxy.java

 @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {// 如果是object方法  ,直接 invoke
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else {//返回mappedMethod(缓存中有,就在缓存中拿,缓存中没有, 就创建) 并执行mappedMethod,即mapperMethod.execute(sqlSession, args);
      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);// 这个步骤可以先不看,如果想看不阻拦,注意方法调用栈即可看懂.
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

MappedMethod.java

根据SQL语句类型 ,执行相应的方法 ,对于mapper.selectBlog(1) 来说, 即select代码块.不想看的话,可以直接跳过.

  • 结论:对于mapper.selectBlog(1) , 会调用 DefaultSqlSession.selectOne(args)方法 ,然后在调用DefaultSqlSession.selectList(args);
//switch: 根据SQL语句类型 ,执行相应的方法 基本步骤构建statement并处理方法参数 以生成SQL语句, 执行SQL命令,映射结果集
 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()) {// 处理无返回值
         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()) {// 处理游标
         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;
     case FLUSH:
       result = sqlSession.flushStatements();
       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;
 }

DefaultsqlSession.java

结论:对于mapper.selectBlog(1) , 会调用 DefaultSqlSession.selectOne(args)方法 ,然后在调用DefaultSqlSession.selectList(args), 然后交给executor 执行query方法.

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
     MappedStatement ms = configuration.getMappedStatement(statement);// 获取mappedStatement
    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();
  }
}

补充说明: Executor 是在 sqlSessionFactory.openSession() 执行初始化时创建的. 即executor = CachingExecutor.

创建executor 方法是:Configuration.newExecutor(Transaction transaction, ExecutorType executorType) ; 有需要的自己看.

public class CachingExecutor implements Executor {

  private final Executor delegate;// simpleExecutor extends baseExecutor
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }

CachingExecutor.java

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);// 获取sql
 //用于一级缓存比较是否相同:具体查看CacheKey.equals()方法 
    		// 即statementid(class 全路径+ 方法名)+ offset()+ limmt + sql 语句+ params
  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) {// SimpleExecutor 去查询
        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);
}

BaseExecutor.java

  • BaseExecutor.query(args)
@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);//处理存储过程 callabelStatement
    } else {//查数据库
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();//处理单条SQL命令的缓存 (ps: 默认返回的是session级别的缓存)
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  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;
  }

SimpleExecutor.java

@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());//对statement 进行处理,绑定参数到sql中
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);//利用事务获取connection
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);// 处理参数 绑定 sql - java type
    return stmt;
  }

PreParedStatement.java

PS: 真正干活的PreParedStaementLogger.java , 依旧是委托模式, 不过有log日志生成.

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();//与数据库交互, 执行操作
  return resultSetHandler.handleResultSets(ps);// 处理结果集 ,将结果集与 resultmap映射,返回list对象这一步有点复杂,
}

PreParedStatementLogger.java

人话解释: 对方法进行分类, 对应执行 PreparedStatement 对应的方法.

 @Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
  try {
    if (Object.class.equals(method.getDeclaringClass())) {// 处理obj 方法
      return method.invoke(this, params);
    }
    if (EXECUTE_METHODS.contains(method.getName())) {// 处理PreParedStatement.execute()|| preparedStatement.executeQuery()等 方法
      if (isDebugEnabled()) {
        debug("Parameters: " + getParameterValueString(), true);
      }
      clearColumnInfo();
      if ("executeQuery".equals(method.getName())) {//preparedStatement.executeQuery()
        ResultSet rs = (ResultSet) method.invoke(statement, params);
        return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
      } else {
        return method.invoke(statement, params);//执行preparedStatement.execute() 方法
      }
    } else if (SET_METHODS.contains(method.getName())) {// 处理preparedStatement.setXXX()方法
      if ("setNull".equals(method.getName())) {
        setColumn(params[0], null);
      } else {
        setColumn(params[0], params[1]);
      }
      return method.invoke(statement, params);
    } else if ("getResultSet".equals(method.getName())) {//处理结果集PreParedStatem.getResultSet()
      ResultSet rs = (ResultSet) method.invoke(statement, params);
      return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
    } else if ("getUpdateCount".equals(method.getName())) {
      int updateCount = (Integer) method.invoke(statement, params);
      if (updateCount != -1) {
        debug("   Updates: " + updateCount, false);
      }
      return updateCount;
    } else {
      return method.invoke(statement, params);// 其他方法
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

总结:

  • Mybatis 默认查询方法的执行流程是: 二级缓存 -> 一级缓存 -> DB

  • 一级缓存 LocalCache: localCacheScope= session( default), 建议修改为 statement ,session 存在脏读现象

  • 二级缓存 Cache: cacheEnabled=true(default), 对应xxxMapper.xml 添加 即可使用,使用二级缓存,可以有效避免在单表查询时出现的脏读现象,但是在多表查询时,极大可能出现脏数据,存在缺陷.

参考:

  • https://tech.meituan.com/2018/01/19/mybatis-cache.html