首页 > 数据库 >【Mybatis】【SQL执行过程】【四】Mybatis源码解析-Insert的执行过程

【Mybatis】【SQL执行过程】【四】Mybatis源码解析-Insert的执行过程

时间:2023-03-08 22:23:51浏览次数:38  
标签:Insert mappedStatement 源码 boundSql ms executor Mybatis parameter rowBounds

1  前言

上节带大家简单回顾了下 SqlSession以及内部的执行器的创建,那么这节我们就开始看我们的语句都是如何执行的。

调试代码:

// xml
<insert id="insertOne" parameterType="org.apache.ibatis.test.po.DemoPo" useGeneratedKeys="true" keyProperty="id">
   insert into ${params.tableName}(name) values (#{params.name});
</insert>
// mapper
void insertOne(@Param("params")DemoPo po);

还记得我们的出发点应该在那里么,就是我们的 Mapper接口的JDK代理增强 MapperProxy里的 invoke,会创建出对应的 MapperMethod 对象然后执行 execute,我们就从这里开始看起,看 insert 都具体做了哪些。

2  源码分析

2.1  通读

那么进入到 SqlSession中来看下具体的执行过程:

/**
 * DefaultSqlSession
 * statement 是什么? 也就是 Mapper全类名+方法名
 * parameter 就是解析后的参数项以及值
 */
public int insert(String statement, Object parameter) {
  // 可以看到 insert 其实也是 update
  return update(statement, parameter);
}
@Override
public int update(String statement, Object parameter) {
  try {
    // dirty 污染,增删改都会置为 true,select 不会
    dirty = true;
    // 获取到语句对应的 MappedStatement 对象
    MappedStatement ms = configuration.getMappedStatement(statement);
    // 调用执行器进行执行
    return executor.update(ms, wrapCollection(parameter));
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

然后我们进入执行器的执行,我们默认的执行器是 SImplExecutor,并且默认是开启了二级缓存,所以我们的执行器是包装了 SimpleExecutor 的 CacheExecutor,那我们进入到 CacheExecutor 来看一下:

// CachingExecutor
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  /**
   * 创建 MappedStatement 对象的时候的属性 flushCacheRequired 查询false 增删改都是true
   * MapperBuilderAssistant 中 flushCacheRequired(valueOrDefault(flushCache, !isSelect))
   * 这里清的是事务缓存  TransactionalCacheManager负责管理,里边维护着 Map<Cache, TransactionalCache>
   * Map<Cache, TransactionalCache> key->cache 就是 mapper 里的<cache/>标签 value->TransactionalCache
   * TransactionalCache:Map<Object, Object> entriesToAddOnCommit;
   * 事务缓存,解决用来解决脏读的(后续单独讲哈)
   */
  flushCacheIfRequired(ms);
  // BaseExecutor  执行器去执行
  return delegate.update(ms, parameterObject);
}

我们会看到有一个方法是 flushCacheIfRequired,用于清理事务缓存的,我们后边会单独讲哈,这里我们先看大体的执行过程哈,我们继续:

// BaseExecutor 
public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  // 更新操作会清掉一级缓存
  clearLocalCache();
  // 执行更新
  return doUpdate(ms, parameter);
}
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    /**
     * 创建的都是 RoutingStatementHandler 只不过里边的 StatementHandler 不一样,操作也是调用对用的 handler 去执行
     * 根据 MappedStatement 类型创建不同的 handler 每种 handler的实例化都会调用父类 BaseStatementHandler 进行实例化
     * STATEMENT -> SimpleStatementHandler
     * PREPARED  -> PreparedStatementHandler **默认
     * CALLABLE  -> CallableStatementHandler
     * 创建 MappedStatement 默认的:StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()))
     *
     * 还会涉及插件哈 插件以后单独说哈
     */
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    /**
     * 一些准备工作
     */
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 执行更新
    return handler.update(stmt);
  } finally {
    closeStatement(stmt);
  }
}

到这里我们看到执行器的执行过程大致是:

  1. 创建 StatementHandler
  2. 执行前的准备工作
  3. 执行

那么我们来看下三者的具体执行操作。

2.2  创建 StatementHandler

我们来看下 StatementHandler 的创建过程:

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  // 创建 StatementHandler
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  // 插件加上
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  switch (ms.getStatementType()) {
    case STATEMENT:
      delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case PREPARED:
      delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case CALLABLE:
      delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    default:
      throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
  }
}
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}

可以看到创建的 StatementHander 都是 RoutingStatementHandler,类型的只不过里边持有的 handler 会根据语句的类型来创建,默认的就是 PreparedStatementHandler,该 handler 的实例化又会调用父类的实例化,我们来看下这几个类的类图,方便理解:

 其实跟执行器的类图结构基本一摸一样,那么我们来看下实例化内都做了什么:

// BaseStatementHandler
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  this.configuration = mappedStatement.getConfiguration();
  this.executor = executor;
  this.mappedStatement = mappedStatement;
  // 分页参数
  this.rowBounds = rowBounds;
  // 类型转换器
  this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  // 对象工厂用于结果操作
  this.objectFactory = configuration.getObjectFactory();
  // 初始化 sql 语句
  if (boundSql == null) { // issue #435, get the key before calculating the statement
    generateKeys(parameterObject);
    boundSql = mappedStatement.getBoundSql(parameterObject);
  }
  this.boundSql = boundSql;
  this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

里边有一个很重要的 boundSql 这个就是我们的 sql 语句,这里需要进行创建,那么我们来看下。

2.2.1  generateKeys

这个是什么呢,我们写 insert 的时候是不是有的时候会获取自增的主键,这个就是辅助我们的,我们看下:

protected void generateKeys(Object parameter) {
  // 获取到主键的生成器
  KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
  ErrorContext.instance().store();
  keyGenerator.processBefore(executor, mappedStatement, null, parameter);
  ErrorContext.instance().recall();
}

那么主键的生成器是怎么来的呢,就是在解析的时候(解析的时候都具体讲过),这里我们简单来看下:

// 解析 <selectKey> 节点 insert 和 update 有这个
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 获取 KeyGenerator 实例
if (configuration.hasKeyGenerator(keyStatementId)) {
  keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
  // 判断有没有开启 useGeneratedKeys 属性,有的话就会赋值Jdbc3KeyGenerator生成器,否则就是 NoKeyGenerator
  keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
      configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
      ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}

主键生成器的类型大致有三种:

  • JDBC3KeyGentor 这个是自增型主键获取  before什么也不做,after 会根据 JDBC返回的结果解析主键值并设置回给你
  • NoKeyGenerator 不做任何操作
  • SelectKeyGenerator 就是我们在 insert 中配置的 SelectKey标签,会被解析成这种类型的生成器

主键生成器有 before 和 after两个时机,也就是这个主键是要在插入动作前设置还是在插入动作后设置,这里如果我们配置了插入前获取,就会通过 SelectKeyGenerator 获取主键值,并设置进你的参数项中。

2.2.2  BoundSql 的创建

语句解析以及赋值,这个比较复杂我们后边会单独的讲,这里我们看下结果:

2.3  执行前的准备工作

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  // 获取连接
  Connection connection = getConnection(statementLog);
  // 统一准备工作 超时时间的设置等
  stmt = handler.prepare(connection, transaction.getTimeout());
  // 各自的特定化准备工作,就是每种 StatementHandler 各自的特性话工作 比如这里就会对我们的占位符进行设置 #{},就不进去看了哈
  handler.parameterize(stmt);
  return stmt;
}

2.4  执行

/**
 * PreparedStatementHandler
 * 这里就会 回归本质了 跟我们以前自己写 JDBC一样的
 */
public int update(Statement statement) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  // 执行
  ps.execute();
  // 获取更新行数
  int rows = ps.getUpdateCount();
  Object parameterObject = boundSql.getParameterObject();
  // 主键生成器 主键赋值
  KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
  keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
  return rows;
}

3  小结

 本节我们大致了解了 insert 的一个执行过程,有理解不对的地方欢迎指正哈。

标签:Insert,mappedStatement,源码,boundSql,ms,executor,Mybatis,parameter,rowBounds
From: https://www.cnblogs.com/kukuxjx/p/17196286.html

相关文章

  • SQL覆盖写入 INSERT ON CONFLICT
    SQL覆盖写入INSERTONCONFLICTONCONFLICTDOUPDATESETcolumn_name={expression|DEFAULT}ONCONFLICTDOUPDATENOTHING[WITH[RECURSIVE]with_query......
  • 持久化技术Mybatis知识精讲【形成知识体系之路】
    环境要求JDK1.8及以上版本MySQL数据库ApacheMaven3.6.1构建工具IDEA/VSCode/Eclipse开发工具任选其一思维导图:XmindZEN技术要求熟悉Java语言熟悉数......
  • 【Mybatis】【SQL执行过程】【三】Mybatis源码解析-SqlSession、Executor的创建
    1 前言上节我们看到 MapperMethod执行的前奏,看到其实都是调用的SqlSession去执行的,而SqlSession又是调用其内部的Executor来进行执行的,那么这节我们先来看下回......
  • mybatis01_mybatis入门
    一、MyBatis简介​ MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apachesoftwarefoundation迁移到了googlecode,并且改名为MyBatis。2013年11月迁移到Github......
  • mybatis02_Mapper代理开发
    1、创建项目并添加依赖、连接数据库,编写mybatis的配置文件项目结构如下所需依赖如下(创建的是聚合工程,请根据自己的是实际情况选择合适的版本)<properties><ma......
  • Mybatis 源码分析
    转自:https://juejin.cn/post/6983853041686577189mybatis是当今Java项目使用最为广泛的ORM框架,免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。本文将会带大家......
  • vuex-router-sync 源码解析
    vuex-router-sync:路由状态管理,保持vue-router和vuex存储同步。import{sync}from'vuex-router-sync'importrouterfrom'@/router'importstorefrom'@/store'syn......
  • mybatis种的ResultMap嵌套
    mybatis中的返回类嵌套一个list,如何实现?<resultMapid="CusMap"type="com.yang.webstarter.entity.SysUser"><collectionproperty="books"javaType="java......
  • Tomcat 中的 NIO 源码分析
    转自:https://javadoop.com/post/tomcat-nio之前写了两篇关于NIO的文章,第一篇介绍了NIO的Channel、Buffer、Selector使用,第二篇介绍了非阻塞IO和异步IO,并展示了简......
  • java springboot mybatis plus 3.4 实现执行任意 sql 语句
    试了SqlRunner一直失败,不知道原因,于是试了如下方法,完美解决。@AutowiredprivateSqlSessionFactorysqlSessionFactory;publicList<Map<String,Object>>exec......