Mybatis3.x 源码阅读-05 insert执行流程

1. 前言

上一篇讲了mapper的注册与获取,这一节一起看看MapperMethod类

2. 正文

2.1 案例

@Test
    public void test(){
        SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtil.getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        PersonDao personDao =  sqlSession.getMapper(PersonDao.class);
        Person p = new Person();
        p.setAddress("广东省");
        p.setAge(12);
        p.setEmail("157538651@qq.com");
        p.setName("chen");
        p.setPhone("15345634565");
        personDao.insert(p);
        System.out.println(p.toString());
        sqlSession.commit();
        sqlSession.close();
    }

2.2 MapperMethod#execute

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {@1
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);@2
        result = rowCountResult(sqlSession.insert(command.getName(), param));@3
        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()) {
          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;
  }

@1 判断你要执行的语句类型走不同是case
@2 获取你对象中的参数,上面案例中就是person的对象值
@3 调动sqlSession的insert方法,然后通过rowCountResult封装insert返回的对象,最终返回result,核心的流程就是这一行,进去看下具体的实现

2.3 常见的接口及实现类

在看具体实现之前,先理下几个核心的接口和实现类

2.3.1 SqlSession

首先是SqlSession,里面包含了经常操作数据库用到的一些方法,比如insert/update/selectOne/select/selectList 等等,默认有2个实现类,默认是DefaultSqlSession
在这里插入图片描述

2.3.2 Executor 执行器

BaseExecutor是抽象类,下面有3个要重点关注下:
SimpleExecutor 简单的执行器,就是执行完Statement会及时关掉,也是mybatis默认的执行器。
ReuseExecutor 复用的执行器,会把用过的Statement 用map存起来,key=sql,value=Statement
BatchExecutor 批量的执行器,为了提高性能,一次性操作多个Statement ,执行完,也会及时关闭。
这三个执行器,可以在mybatis 中配置,一种方式是在mybatis-config.xml 配置,另外一种是在spring xml 中配置sqlSession的时候配置。
还有一个特殊的,就是CachingExecutor ,它是把MappedStatement 缓存起来了。
在这里插入图片描述
注意:他里面持有了一个特殊的对象,private final Executor delegate;
还记得之前获取session的时候,构建的执行器吧,就是下面这个代码

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);@1
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);@2
    } else {
      executor = new SimpleExecutor(this, transaction);@3
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);@4
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

@1、@2、@3 对象构建好后,只要是开启了缓存(默认开启),则会对executor 加一层包装,从而变成CachingExecutor,到这里就豁然开朗了吧,也就是上面案例中CachingExecutor 中的delegate 其实就是SimpleExecutor(默认是简单的执行器);

2.3.2 StatementHandler

这几个子类其实就是对应jdbc里面的几种Statement
SimpleStatementHandler 简单SQL的处理;
PreparedStatementHandler 预编译SQL处理;
CallableStatementHandler JDBC中CallableStatement,执行存储过程相关的接口
RoutingStatementHandler 是核心,也是负责把上面三个handle串起来的关键类,主要负责上面三个handle的创建和调用,所以他也和CachingExecutor类似,持有一个
private final StatementHandler delegate;
## 2.4  常见的接口及实现类

2.3.3 KeyGenerator

KeyGenerator的功能是生成数据库主键和将insert 生成的主键设置到pojo中
在这里插入图片描述
重点关注2个实现类:Jdbc3KeyGenerator、SelectKeyGenerator
Jdbc3KeyGenerator表示自增的,也就是数据库自增后如果需要知道值,比如mysql 表主键自增。
这个是将自增结果回填到对象中是从返回的Statement中获取id值。配置方式:在 insert 标签中配置了keyProperty="id" useGeneratedKeys="true"属性

<insert id="insert" parameterType="Person" keyProperty="id" useGeneratedKeys="true">
        INSERT INTO person (name, age, phone, email, address)
        VALUES(#{name},#{age},#{phone},#{email},#{address})
    </insert>

SelectKeyGenerator 是通过insert后,再查询一次,来获取id的值,Oracle 需要结合sequence 来设置主键,就可以用SelectKeyGenerator方式配置。配置方式:在insert标签内,配置selectKey标签

<insert id="insert" parameterType="Person" keyProperty="id" useGeneratedKeys="true">
		<selectKey resultType="int" keyProperty="id" order="BEFORE">
     				SELECT LAST_INSERT_ID()  AS id 
  		</selectKey>
        INSERT INTO person (name, age, phone, email, address)
        VALUES(#{name},#{age},#{phone},#{email},#{address})
    </insert>

BEFORE 表示在插入之前,先查询一次获取到id

2.4 核心流程

2.4.1 继续回到上面2.2

        Object param = method.convertArgsToSqlCommandParam(args);@2
        result = rowCountResult(sqlSession.insert(command.getName(), param));@3

进入到insert中

@Override
public int insert(String statement, Object parameter) {
  return update(statement, parameter);
}

@Override
public int update(String statement, Object parameter) {
  try {
    dirty = true;
    MappedStatement ms = configuration.getMappedStatement(statement);@1
    return executor.update(ms, wrapCollection(parameter));@2
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

@1 从config中获取mapper的信息
@2 这里的executor是CachingExecutor,原因上面解释过了。进入到CachingExecutor#update

2.4.2 CachingExecutor#update

@Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);@1
    return delegate.update(ms, parameterObject);@2
  }
private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {
      tcm.clear(cache);
    }
  }

@1 刚刚说过的, MappedStatement 是有缓存的,这里需要进行是否需要clear,isFlushCacheRequired 参数是在创建缓存的时候,就需要设置的。
@2 调用抽象类BaseExecutor的update方法

2.4.3 BaseExecutor#update

 @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());@1
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();@2
    return doUpdate(ms, parameter);@3
  }

@1 记录上下文信息,用于抛异常的时候,获取信息。内部通过ThreadLocal来存储
@2 清除本地缓存
@3 调用子类的doUpdate方法

2.4.4 SimpleExecutor#doUpdate

@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);@1
    stmt = prepareStatement(handler, ms.getStatementLog());@2
    return handler.update(stmt);@3
  } 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);@4
  return stmt;
}

@1 构建StatementHandler 对象,这里会根据配置的Statement拦截器,执行所有的拦截后,返回StatementHandler
@2 构建Statement对象,进入到prepareStatement方法,里面有@4,这里是设置参数处理handle
最终的执行方法如下:
DefaultParameterHandler#setParameters

@Override
  public void setParameters(PreparedStatement ps) {
    //记录上下文
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    //获取需要填充的参数的个数(就是?的个数)
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        //?是输入参数,进入if
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          //判断SQL中是否包含此属性 
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            //取值
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          //获取参数类型
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            //给pre 设置值
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

进入PreparedStatementHandle update 方法

2.4.5 PreparedStatementHandle#update

@Override
public int update(Statement statement) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();@1
  int rows = ps.getUpdateCount();@2
  Object parameterObject = boundSql.getParameterObject();@3
  KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();@4
  keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);@5
  return rows;
}

@1 执行jdbc execute
@2 返回受影响的记录条数
@3 获取插入的对象,上述案例中的person对象
@4 获取主键生成器,设置person id字段的值为刚刚插入的主键

2.4.6 继续回到

        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));

接下来是sqlSession.insert 返回受影响的记录条数,然后通过rowCountResult 返回一个通用的result.
处理逻辑如下:

private Object rowCountResult(int rowCount) {
    final Object result;
    if (method.returnsVoid()) {
      result = null;
    } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
      result = rowCount;
    } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
      result = (long)rowCount;
    } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
      result = rowCount > 0;
    } else {
      throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    }
    return result;
  }

根据方法的要求的返回类型,处理返回值。

3. 总结

insert 时序图
在这里插入图片描述

  1. 发起insert 请求,获取mapper代理对象
  2. 调用代理对象的invoke
  3. 执行MapperMethod的execute,根据sql语句类型,执行不同case
  4. DefaultSqlSession#insert 底层调用的也是DefaultSqlSession#update(可以参考DefaultSqlSession.java 183行),调用执行器的update方法
  5. 获取默认开启了缓存,所以是调用CachingExecutor#update
  6. 调用抽象类的BaseExecutor#update
  7. 调用子类的具体实现SimpleExecutor#doUpdate
  8. 执行后,逐步返回受影响的记录条数

4. 参考

[1] https://blog.csdn.net/Roger_CoderLife/article/details/88835765
[2] https://blog.csdn.net/yangliuhbhd/article/details/80982254

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值