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是通过StatementgetGeneratedKeys方法来获取。通过debug发现在有多个对象写入的时候,getGeneratedKeys只返回了一个id。
查看 JDBC驱动中的getGeneratedKeys方法,ResultSet 中的id是通过执行的结果对象OkPacketgetLastInsertId 方法来获取.

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。
推荐使用每一条 批处理的方式写入。

posted @ 2024-03-07 00:52  小七君12138  阅读(63)  评论(0)    收藏  举报