MybatisPlus的insert执行之后,id是怎么获取的?

2024年 1月 3日 100.5k 0

在日常开发中,会经常使用Mybatis-Plus

当简单的插入一条记录时,使用mapper的insert是比较简洁的写法

@Data
public class NoEo {
    Long id;
    String no;
}
NoEo noEo = new NoEo();
noEo.setNo("321");
noMapper.insert(noEo);
System.out.println(noEo);

这里可以注意到一个细节,就是不管我们使用的是什么类型的id,好像都不需要去setId,也能执行insert语句

不仅不需要setId,在insert语句执行完毕之后,我们还能通过实体类获取到这条insert的记录的id是什么

image.png

image.png

这背后的原理是什么呢?

自增类型ID

刚学Java的时候,插入了一条记录还要再select一次来获取这条记录的id,比较青涩

后面误打误撞才发现可以直接从insert的实体类中拿到这个id

难道框架是自己帮我查了一次嘛

先来看看自增id的情况

首先要先把yml中的mp的id类型设置为auto

mybatis-plus:
  global-config:
    db-config:
      id-type: auto

然后从insert语句开始一直往下跟进

noMapper.insert(noEo);

后面会来到这个方法

// com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor#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);
        stmt = prepareStatement(handler, ms.getStatementLog(), false);
        return stmt == null ? 0 : handler.update(stmt);
    } finally {
        closeStatement(stmt);
    }
}

在执行了下面这个方法之后

handler.update(stmt)

实体类的id就赋值上了

继续往下跟

// org.apache.ibatis.executor.statement.PreparedStatementHandler#update
@Override
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;
}

image.png

最后的赋值在这一行

keyGenerator.processAfter

可以看到会有一个KeyGenerator做一个后置增强,它具体的实现类是Jdbc3KeyGenerator

// org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processAfter
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  processBatch(ms, stmt, parameter);
}
// org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processBatch
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
  final String[] keyProperties = ms.getKeyProperties();
  if (keyProperties == null || keyProperties.length == 0) {
    return;
  }
  try (ResultSet rs = stmt.getGeneratedKeys()) {
    final ResultSetMetaData rsmd = rs.getMetaData();
    final Configuration configuration = ms.getConfiguration();
    if (rsmd.getColumnCount() < keyProperties.length) {
      // Error?
    } else {
      assignKeys(configuration, rs, rsmd, keyProperties, parameter);
    }
  } catch (Exception e) {
    throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
  }
}
// org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#assignKeys
private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
    Object parameter) throws SQLException {
  if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
    // Multi-param or single param with @Param
    assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
  } else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
      && ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
    // Multi-param or single param with @Param in batch operation
    assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, (ArrayList<ParamMap<?>>) parameter);
  } else {
    // Single param without @Param
    // 当前case会走这里
    assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
  }
}
// org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#assignKeysToParam
private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
    String[] keyProperties, Object parameter) throws SQLException {
  Collection<?> params = collectionize(parameter);
  if (params.isEmpty()) {
    return;
  }
  List<KeyAssigner> assignerList = new ArrayList<>();
  for (int i = 0; i < keyProperties.length; i++) {
    assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i]));
  }
  Iterator<?> iterator = params.iterator();
  while (rs.next()) {
    if (!iterator.hasNext()) {
      throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, params.size()));
    }
    Object param = iterator.next();
    assignerList.forEach(x -> x.assign(rs, param));
  }
}
// org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.KeyAssigner#assign
protected void assign(ResultSet rs, Object param) {
  if (paramName != null) {
    // If paramName is set, param is ParamMap
    param = ((ParamMap<?>) param).get(paramName);
  }
  MetaObject metaParam = configuration.newMetaObject(param);
  try {
    if (typeHandler == null) {
      if (metaParam.hasSetter(propertyName)) {
        // 获取主键的类型
        Class<?> propertyType = metaParam.getSetterType(propertyName);
        // 获取主键类型处理器
        typeHandler = typeHandlerRegistry.getTypeHandler(propertyType,
            JdbcType.forCode(rsmd.getColumnType(columnPosition)));
      } else {
        throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
            + metaParam.getOriginalObject().getClass().getName() + "'.");
      }
    }
    if (typeHandler == null) {
      // Error?
    } else {
      // 获取主键的值
      Object value = typeHandler.getResult(rs, columnPosition);
      // 设置主键值
      metaParam.setValue(propertyName, value);
    }
  } catch (SQLException e) {
    throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e,
        e);
  }
}
// com.mysql.cj.jdbc.result.ResultSetImpl#getObject(int, java.lang.Class<T>)
@Override
public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
        // ...
        else if (type.equals(Long.class) || type.equals(Long.TYPE)) {
            checkRowPos();
            checkColumnBounds(columnIndex);
            return (T) this.thisRow.getValue(columnIndex - 1, this.longValueFactory);

        }
        // ...
}

image.png

最后可以看到这个自增id是在ResultSet的thisRow里面

然后后面的流程就是去解析这个字节数据获取这个long的id

就不往下赘述了

雪花算法ID

yml切换回雪花算法

mybatis-plus:
  global-config:
    db-config:
      id-type: assign_id

在使用雪花算法的时候,也是会走到这个方法

// com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor#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);
        stmt = prepareStatement(handler, ms.getStatementLog(), false);
        return stmt == null ? 0 : handler.update(stmt);
    } finally {
        closeStatement(stmt);
    }
}

但是不同的是,执行完这一行之后,实体类的id字段就已经赋值上了

StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);

image.png

继续往下跟进

// org.apache.ibatis.session.Configuration#newStatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}
// org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

  switch (ms.getStatementType()) {
    // ...
    case PREPARED:
      delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    // ...
  }

}

最后跟进到一个构造器,会有一个processParameter的方法

// com.baomidou.mybatisplus.core.MybatisParameterHandler#MybatisParameterHandler
public MybatisParameterHandler(MappedStatement mappedStatement, Object parameter, BoundSql boundSql) {
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    this.mappedStatement = mappedStatement;
    this.boundSql = boundSql;
    this.configuration = mappedStatement.getConfiguration();
    this.sqlCommandType = mappedStatement.getSqlCommandType();
    this.parameterObject = processParameter(parameter);
}

在这个方法里面会去增强参数

// com.baomidou.mybatisplus.core.MybatisParameterHandler#processParameter
public Object processParameter(Object parameter) {
    /* 只处理插入或更新操作 */
    if (parameter != null
        && (SqlCommandType.INSERT == this.sqlCommandType || SqlCommandType.UPDATE == this.sqlCommandType)) {
        //检查 parameterObject
        if (ReflectionKit.isPrimitiveOrWrapper(parameter.getClass())
            || parameter.getClass() == String.class) {
            return parameter;
        }
        Collection<Object> parameters = getParameters(parameter);
        if (null != parameters) {
            parameters.forEach(this::process);
        } else {
            process(parameter);
        }
    }
    return parameter;
}
// com.baomidou.mybatisplus.core.MybatisParameterHandler#process
private void process(Object parameter) {
    if (parameter != null) {
        TableInfo tableInfo = null;
        Object entity = parameter;
        if (parameter instanceof Map) {
            Map<?, ?> map = (Map<?, ?>) parameter;
            if (map.containsKey(Constants.ENTITY)) {
                Object et = map.get(Constants.ENTITY);
                if (et != null) {
                    entity = et;
                    tableInfo = TableInfoHelper.getTableInfo(entity.getClass());
                }
            }
        } else {
            tableInfo = TableInfoHelper.getTableInfo(parameter.getClass());
        }
        if (tableInfo != null) {
            //到这里就应该转换到实体参数对象了,因为填充和ID处理都是争对实体对象处理的,不用传递原参数对象下去.
            MetaObject metaObject = this.configuration.newMetaObject(entity);
            if (SqlCommandType.INSERT == this.sqlCommandType) {
                populateKeys(tableInfo, metaObject, entity);
                insertFill(metaObject, tableInfo);
            } else {
                updateFill(metaObject, tableInfo);
            }
        }
    }
}

最终生成id并赋值的操作是在populateKeys中

// com.baomidou.mybatisplus.core.MybatisParameterHandler#populateKeys
protected void populateKeys(TableInfo tableInfo, MetaObject metaObject, Object entity) {
    final IdType idType = tableInfo.getIdType();
    final String keyProperty = tableInfo.getKeyProperty();
    if (StringUtils.isNotBlank(keyProperty) && null != idType && idType.getKey() >= 3) {
        final IdentifierGenerator identifierGenerator = GlobalConfigUtils.getGlobalConfig(this.configuration).getIdentifierGenerator();
        Object idValue = metaObject.getValue(keyProperty);
        if (StringUtils.checkValNull(idValue)) {
            if (idType.getKey() == IdType.ASSIGN_ID.getKey()) {
                if (Number.class.isAssignableFrom(tableInfo.getKeyType())) {
                    metaObject.setValue(keyProperty, identifierGenerator.nextId(entity));
                } else {
                    metaObject.setValue(keyProperty, identifierGenerator.nextId(entity).toString());
                }
            } else if (idType.getKey() == IdType.ASSIGN_UUID.getKey()) {
                metaObject.setValue(keyProperty, identifierGenerator.nextUUID(entity));
            }
        }
    }
}

在tableInfo中可以得知Id的类型

如果是雪花算法类型,那么生成雪花id;UUID同理

image.png

总结

insert之后,id被赋值到实体类的时机要根据具体情况具体讨论:

如果是自增类型的id,那么要在插入数据库完成之后,在ResultSet的ByteArrayRow中获取到这个id

如果是雪花算法id,那么在在插入数据库之前,会通过参数增强的方式,提前生成一个雪花id,然后赋值给实体类

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论