Mybatis 批量插入自增ID回写异常(mariadb)
最近在做批量写入的时候,为了能提高效率采用了将多笔数据一次性写入的方案,SQL 使用如下方式
<insert id="insertUsers" parameterType="top.fantuan.mybaits.example.model.User"
useGeneratedKeys="true" keyProperty="id">
insert into user(user_name) values
<trim suffixOverrides=",">
<foreach collection="list" item="item" separator=",">
(#{item.userName})
</foreach>
</trim>
</insert>
但是在执行完成之后无法正常获取生成的自增ID.
Mybatis针对这种自增ID 默认使用Jdbc3KeyGenerator 来回写自增ID到对象。
public int update(Statement statement) throws SQLException {
//...
if (keyGenerator instanceof Jdbc3KeyGenerator) {
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
}
//...
return rows;
}
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
processBatch(ms, stmt, parameter);
}
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
// 首先判断Mapper中是否设置了KeyProperties
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
// 获取通过 Statement 获取生成的 keys
try (ResultSet rs = stmt.getGeneratedKeys()) {
// ...
if (rsmd.getColumnCount() < keyProperties.length) {
// Error?
} else {
// 将获取的值 赋值到对应的属性
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
//...
}
}
从上述代码可以看到自增ID是通过Statement的getGeneratedKeys方法来获取。通过debug发现在有多个对象写入的时候,getGeneratedKeys只返回了一个id。
查看 JDBC驱动中的getGeneratedKeys方法,ResultSet 中的id是通过执行的结果对象OkPacket的getLastInsertId 方法来获取.
public ResultSet getGeneratedKeys() throws SQLException {
//...
List<String[]> insertIds = new ArrayList<>();
if (con.getContext().getConf().returnMultiValuesGeneratedIds()
&& !checkIfInsertDuplicateCommand(getLastSql())) {
// ...
} else {
// standard behavior
// currResult 单次执行的结果
// OkPacket https://mariadb.com/kb/en/ok_packet/
if (currResult instanceof OkPacket && ((OkPacket) currResult).getLastInsertId() != 0) {
insertIds.add(new String[] {String.valueOf(((OkPacket) currResult).getLastInsertId())});
}
// results executeBatch 执行的时候每次执行的结果的数组
if (results != null) {
for (Completion result : results) {
if (result instanceof OkPacket && ((OkPacket) result).getLastInsertId() != 0) {
insertIds.add(new String[] {String.valueOf(((OkPacket) result).getLastInsertId())});
}
}
}
}
//.....
}
查看官方文档对 last_insert_id 的说明
在一个insert 后面多个values 的状况,last_insert_id 只会返回第一笔数据生成的id。
INSERT INTO t(f) VALUES('d'),('e');
SELECT LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
| 4 |
+------------------+
所以在使用一次insert 写入对多个value 的状况无法正常获取生成的自增ID。
推荐使用每一条 批处理的方式写入。
浙公网安备 33010602011771号