MyBatis 这小子是怎样拿到 insert 时生成的主键的?

文章概要

SQL 语句执行insert,这谁都会,执行之后,返回结果是影响行数。但是在我们应用开发场景中,有些表的主键采用自增生成。这个时候,你怎么能把这个自增的主键值拿到呢?

你说这还不简单,马上再执行一下 select,就都查出来啦。 你的where 是什么才限定刚好是新增的那一条呢? 你说,那我查主键值最大的那一条呢? 也不灵。毕竟insert 和select 之间是有时间差的,此时可能有其他写入操作。

一些常用的 ORM 框架在执行完 save操作之后,一般都会把新生成的主键自动回填到PO对象中。那它们是怎么做到的呢?

带着这个疑问,咱们一起来看一下,时下活跃在各大应用中的明星MyBatis。

MyBatis 是如何取到自增主键值的
我们知道, MyBatis 无论通过注解,还是XML配置的形式,将待执行的SQL包装起来,在执行时通过代理进行解析。
之前的文章中(与MyBatis缠斗的几个小时…),我们分析过MyBatis的大致执行原理,为何只写一个接口就能映射到一个XML或者注解中对应的SQL逻辑上。
其中有个类MappedStatement,我们在XML里配置的各类SQL 都会转成这样一个实例。这个类中包含了大量配置相关的信息。

我们摘一段代码来看看:

public final class MappedStatement {

private String resource;
private Configuration configuration;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;

MappedStatement() {
// constructor disabled
}

public static class Builder {
private MappedStatement mappedStatement = new MappedStatement();

public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
  mappedStatement.configuration = configuration;
  mappedStatement.id = id;
  mappedStatement.sqlSource = sqlSource;
  mappedStatement.statementType = StatementType.PREPARED;
  mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
  mappedStatement.resultMaps = new ArrayList<ResultMap>();
  mappedStatement.sqlCommandType = sqlCommandType;
  mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  String logId = id;
  if (configuration.getLogPrefix() != null) {
    logId = configuration.getLogPrefix() + id;
  }
  mappedStatement.statementLog = LogFactory.getLog(logId);
  mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
}

代码比较长,咱们长话短说,重点看这一句

mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
这里根据配置时指定是否使用UseGeneratedKeys来决定生成的 keyGenerator实例是谁。

咱们使用时,在 XML 里的 insert 一般写成这样:

insert into user( name, password, age, deleteFlag) values(#{name}, #{password}, #{age}, #{deleteFlag}) 这个时候, insert 这个 XML 标签包含的一些配置就会使用默认值,其中有一个配置项就是咱们上面在MappedStatement中看到的useGeneratedKeys。它的默认值是false。也就是说,这种配置情况下,这个插入操作的自增主键并不会回写回来。 这个配置主要作用:

(仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。

那咱们把它改成true,同时把自增的列和对应的PO里的属性名写上就可以了。再执行一次操作,在insert 之后, PO 里对应的主键就已经自动填充了。
他是在哪一步做的呢?

MyBatis 如何从 XML的方法映射到 SQLCommand,然后从SQLCommand 的SQLType 再执行不同的操作这些,咱们不细说,具体INSERT,也会执行到PreparedStatementHandler类中的这个update方法。

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;
}
请留意KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); 这一句,就是咱们在创建mappedStatement时,根据 useKeyGenerator配置生成的。
在 keyGenerator.processAfter方法中,会有一个操作。这里的keyGenerator是根据配置来决定的,如果没有配置useGeneratedKeys=true的话,那这里返回的就是NoKeyGenerator,配置的话,则返回Jdbc3KeyGenerator

位于 Jdbc3KeyGenerator类中, 我们看到,还是继续持有执行SQL的 Statement,然后 通过执行其getGeneratedKeys方法,来拿到一些数据。

public void processBatch(MappedStatement ms, Statement stmt, Collection parameters) {
ResultSet rs = null;
try {
rs = stmt.getGeneratedKeys();
final Configuration configuration = ms.getConfiguration();
final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
final String[] keyProperties = ms.getKeyProperties();
final ResultSetMetaData rsmd = rs.getMetaData();
TypeHandler<?>[] typeHandlers = null;
if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
for (Object parameter : parameters) {
// there should be one row for each statement (also one for each parameter)
if (!rs.next()) {
break;
}
final MetaObject metaParam = configuration.newMetaObject(parameter);
if (typeHandlers == null) {
typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);
}
populateKeys(rs, metaParam, keyProperties, typeHandlers);
}
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
} finally {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
// ignore
}
}
}
}
重点就是这一句:rs = stmt.getGeneratedKeys();。

Java 文档里描述如下:

Retrieves any auto-generated keys created as a result of executing this Statementobject. If this Statement object did not generate any keys, an empty ResultSetobject is returned.

和我们在前面看到对于useGeneratedKeys的说明描述一致,即通过JDBC API 的方法来拿到自增生成的值,在拿到rs,也就是标准的 JDBC ResultSet 之后,操作和一般的JDBC一样,通过读取columnName来取值。所以我们在前面配置的地方,也需要加上keyColumn。

这就是 MyBatis 获取自增主键的全部秘密。

原文链接

https://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng%3D%3D&mid=2650860098&idx=1&sn=c2fbb1d2d30235c4fbcdaaf0a922b324&chksm=f1329591c6451c871dedaf6400bc1289492200ed06d60c336f58d03e27d105f9bf4358b09e22&mpshare=1&scene=23&srcid=%23rd

服务推荐

猜你喜欢

转载自blog.csdn.net/gt9000/article/details/85991980
今日推荐