深入理解 MyBatis 启动流程(二)

简介: 深入理解 MyBatis 启动流程(二)

执行Map -- maperProxy#


上一个模块我们知道了Mybatis为我们创建出来了mapper接口的代理对象,那当我们获取到这个代理对象之后执行它的mapper.findAll();实际上触发的是代理对象的invoke()方法


所以说,接着看上面的MapperProxyinvoke()方法:


@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);
return mapperMethod.execute(sqlSession, args);
}


上面的方法我们关注两个地方,第一个地方就是final MapperMethod mapperMethod = cachedMapperMethod(method);,见名知意: 缓存MapperMethod

第一个问题: 这个MapperMethod是什么? 它其实是Mybatis为sql命令+方法全限定名设计的封装类


*/
public class MapperMethod {
  private final SqlCommand command;
  private final MethodSignature method;


说白了,就是想为MapperProxy保存一份map格式的信息,key=方法全名 value=MapperMethod(command,method),存放在MapperProxy的属性methodCache中


comand -> "name=com.changwu.dao.IUserDao,fingAll"

method -> "result= public abstract java.util.List.com.changwu.dao.IUserDao.findAll()"


为什么要给这个MapperProxy保存这里呢? 很简单,它是mapper的代理对象啊,程序员使用的就是他,在这里留一份method的副本,再用的话多方便?


完成缓存后,继续看它如何执行方法:,跟进mapperMethod.execute(sqlSession, args)


因为我使用的是@Select("select * from user"),所以一定进入下面的result = executeForMany(sqlSession, args);


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 SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        ...
}


所以我们继续关注这个 executeForMany(sqlSession, args);方法,看他的第一个参数是sqlSession,也就是我们的DefaultSqlSession,他里面存在两大重要对象: 1是configuration 配置对象, 2是Executor 执行器对象

继续跟进:


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);
    }


我们在继续跟进这个selectList方法之前,先看看这个command.getName()是啥? 其实我们上面的代码追踪中有记录: 就是name=com.changwu.dao.IUserDao,fingAll

继续跟进去到下面的方法:


这个方法就比较有趣了,首先来说,下面的入参都是什么


statement = "com.changwu.dao.IUserDao.findAll"parameter=null

第二个问题:MappedStatement是什么? 它是一个对象,维护了很多很多的配置信息,但是我们关心它里面的两条信息,这其实可以理解成一种方法与sql之间的映射,如下图



@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      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();
    }
  }


前面阅读时,我们知道Mybatis为我们创建的默认的执行器 Executor是CachingExecutor,如下图



继续跟进,主要做了下面三件事, 获取到绑定的sql,然后调用SimpleExecutor缓存key,然后继续执行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);
  }


接着调用SimpleExecutorquery()方法,然后我们关注SimpleExecutordoQuery()方法,源码如下


@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);
    }
  }


我们注意到.在SimpleExcutor中创建的了一个 XXXStatementHandler这样一个处理器, 所以我们的只管感觉就是,sql真正执行者其实并不是Executor,而是Executor会为每一条sql的执行重新new 一个 StatementHandler ,由这个handler去具体的执行sql

关于这个StatementHandler到底是是何方神圣? 暂时了解它是Mybatis定义的一个规范接口,定义了如下功能即可


public interface StatementHandler {
  // sql预编译, 构建statement对象 
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
  // 对prepare方法构建的预编译的sql进行参数的设置
  void parameterize(Statement statement)
      throws SQLException;
  // 批量处理器
  void batch(Statement statement)
      throws SQLException;
  // create update delete
  int update(Statement statement)
      throws SQLException;
  // select
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;
  // 获取sql的封装对象 
  BoundSql getBoundSql();
  // 获取参数处理对象
  ParameterHandler getParameterHandler();
}


了解了这个StatementHandler是什么,下一个问题就是当前我们创建的默认的statement是谁呢? routingStatementHandler如下图



创建preparedStatement#


马上马就发生了一件悄无声息的大事!!!根据现有的sql等信息,构建 PreparedStatement,我们关注这个prepareStatement(handler, ms.getStatementLog());方法,通过调试我们得知,prepareStatement()RoutingStatementHandler的抽象方法,被PreparedStatementHandler重写了,所以我们去看它如何重写的,如下:


@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
  statement = instantiateStatement(connection);


我们关注这个instantiateStatement()方法, 并且进入它的connection.prepareStatement(sql);方法,如下图:



纯洁的微笑... 见到了原生JDK, jdbc的亲人...

创建完事这个 preparedStatement,下一步总该执行了吧...绕这么多圈...

我们回到上面代码中的return handler.query(stmt, resultHandler);准备执行,此时第一个参数就是我们的刚创建出来的PreparedStatement, 回想一下,上面创建的这个默认的statement中的代表是PreparedStatementHandler,所以,我们进入到这个StatementHandler的实现类RountingStatementHandler中,看他的query()方法


@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }


调用RountingStatementHandler中维护的代表的StatementHandler也就是PreparedStatementHandlerquery()方法,顺势跟进去,最终会通过反射执行jdbc操作,如图, 我圈出来的对象就是我们上面创建出来的preparedStatement



提交事务#


跟进conmit()方法,分成两步

  • 清空缓存
  • 提交事务

清空缓存是在CachingExecutor中调用了SimpleExecutor简单执行器的方法commit(required)


@Override
  public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    tcm.commit();
  }


接在SimpleExecutor的父类BaseExecutor中完成


@Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }


提交事务的操作在tcm.commit();中完成

相关文章
|
2月前
|
Java 数据库连接 数据库
spring复习05,spring整合mybatis,声明式事务
这篇文章详细介绍了如何在Spring框架中整合MyBatis以及如何配置声明式事务。主要内容包括:在Maven项目中添加依赖、创建实体类和Mapper接口、配置MyBatis核心配置文件和映射文件、配置数据源、创建sqlSessionFactory和sqlSessionTemplate、实现Mapper接口、配置声明式事务以及测试使用。此外,还解释了声明式事务的传播行为、隔离级别、只读提示和事务超时期间等概念。
spring复习05,spring整合mybatis,声明式事务
|
XML Java 数据库连接
SpringBoot-19-Mybatis的xml配置方式
在上一章节中,我们已经简单介绍mybatis的增删改查的基本操作,基础(单表)的增删改查可以按照,如果稍微复杂一些我们就需要使用mybatis的xml格式去实现。 那么我们开始使用mybatis的xml方式去实现增删改查。
104 0
|
6月前
|
SQL Java 数据库连接
MyBatis源码篇:mybatis拦截器源码分析
MyBatis源码篇:mybatis拦截器源码分析
|
6月前
|
XML Java 数据库连接
MyBatis-Spring的实现方式有哪些?
MyBatis-Spring的实现方式有哪些?
31 0
|
Java 数据库连接 数据库
【Spring学习笔记 八】Spring整合MyBatis实现方式
【Spring学习笔记 八】Spring整合MyBatis实现方式
89 0
|
Java 数据库连接 程序员
【Spring学习笔记 八】Spring整合MyBatis实现方式(下)
【Spring学习笔记 八】Spring整合MyBatis实现方式(下)
67 0
|
XML SQL 安全
|
设计模式 SQL 安全
MyBatis-整合Spring的原理分析
MyBatis-整合Spring的原理分析
MyBatis-整合Spring的原理分析
|
Java 数据库连接 Spring
Spring整合Mybatis,SqlSessionDaoSupport方式
SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate🙌 接口实现类:(此类继承SqlSessionDaoSupport即可)
254 2
|
XML Java 数据库连接
Spring整合Mybatis,SqlSessionTemplate方式
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring
367 2