saveBatch自增ID,代码中设置了id后,执行后id被替换成错误的(使用了rewriteBatchedStatements=true)这个问题到底解决没有.

Originally posted by @isMrZhang in https://github.com/baomidou/mybatis-plus/issues/4983#issuecomment-1478969468

当前使用版本(必填,否则不予处理)

3.5.2 MySql8.0

该问题是如何引起的?(确定最新版也有问题再提!!!)

目前就是最新版了,saveBatch的问题,实体的主键id是自增长的,但是saveBatch时,设置了id,saveBatch成功后,再看list数据,里面的id错乱了,从第二条开始就不正确了。检查出问题是因为MySql jdbc url 后面增加了&rewriteBatchedStatements=true,如果去掉该配置,则可以达到预期,但是saveBatch本质上还是单条插入有效率问题。

重现步骤(如果有就写完整)

前提:MySql jdbc url 后面要有&rewriteBatchedStatements=true,然后表主键也是自增id,实体entity里面的id属性也增加了@TableId(type = IdType.AUTO)注解,然后初始化list 12条数据,设置按顺序设置id, 273 274 275 276 277 278 279 280 281 282 283 284 然后调用saveBatch保存,打印的sql 日志,参数都是正确的,都是带id的,执行结束后,再看list中实体对象的id,变成了如下: 273 280 281 282 283 284 285 286 284 285 286 287 每次都特别规律,第一条id正确,第二条开始发生偏差,相差6,然后最后几条开始重复。

报错信息

没有报错,但是list中的id很奇怪

Comment From: wsliliang

我测过了,这个不是mybatisPlus的bug,用原始的PreparedStatment执行也是一样的,我用mysql-connector-5.1.40和你提供的数据测试,返回的id结果是284,285...

看mysql驱动的源码,返回的lastInsertId是284,mysq驱动中使用284这个值,再获取到自增的步长,自己在内存递增的。

Comment From: wsliliang

测试代码: public static void main(String[] args) throws SQLException, ClassNotFoundException { // mysql版本:5.7.35 // mysql-connector-java版本:5.1.40 String url = "jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC"; String user = "root"; String password = "123456"; Class.forName("com.mysql.jdbc.Driver"); String sql = "INSERT INTO user (id, name) VALUES (?, ?)"; Connection conn = DriverManager.getConnection(url, user, password); PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); conn.setAutoCommit(false); for (int i = 1; i <= 12; i++) { stmt.setInt(1, 172 + i); stmt.setString(2, "name" + i); stmt.addBatch(); } stmt.executeBatch(); ResultSet rs = stmt.getGeneratedKeys(); while (rs.next()) { Object value = rs.getObject(1); System.out.println("生成的id:" + value); } conn.commit(); }


运行结果: 生成的id:184 生成的id:185 生成的id:186 生成的id:187 生成的id:188 生成的id:189 生成的id:190 生成的id:191 生成的id:192 生成的id:193 生成的id:194 生成的id:195

Comment From: wsliliang

但是是不是可以在参数有id的情况下,取消自动给实体类设置id的操作, 来解决这个问题?@miemieYaho 如果能接受这样改的话,我愿意尝试修改

Comment From: timnick-snow

四个条件复现这个问题

  1. 数据库表主键设定为自增
  2. @TableId注解中的type也设定为IdType.AUTO
  3. jdbc url 中有 rewriteBatchedStatements=true
  4. 批量插入时,手动设置了id

Comment From: isMrZhang

但是是不是可以在参数有id的情况下,取消自动给实体类设置id的操作, 来解决这个问题?©版权所有2019 -保留所有权利 如果能接受这样改的话,我愿意尝试修改

是啊,希望是这样,在使用自增ID类型的情况下也可以手动为ID赋值

Comment From: isMrZhang

四个条件复现这个问题

  1. 数据库表主键设定为自增
  2. @TableId注解中的type也设定为IdType.AUTO
  3. jdbc url 中有 rewriteBatchedStatements=true
  4. 批量插入时,手动设置了id

专业!

Comment From: wsliliang

@miemieYaho 我用了一个取巧的方式解决了这个问题,麻烦帮忙看一下,如果可以的话,我提交一个PR。 覆盖了mybatis的Jdbc3KeyGenerator类,如果ID已经有值,就不再赋值 MyBatis-Plus saveBatch自增ID,代码中设置了id后,执行后id被替换成错误的(使用了MySql jdbcUrl rewriteBatchedStatements=true)这个问题到底解决没有

Comment From: miemieYaho

你这种可以试试提给mybatis

Comment From: wsliliang

你这种可以试试提给mybatis

@miemieYaho 谢谢您的建议!

Comment From: wsliliang

@miemieYaho 您好,我给mybatis提了,mybatis认为这个是用法不对,他们不建议修改,看看mybatiplus是否考虑修改

Comment From: miemieYaho

如果list里只有前后没set过id,中间自己set了id,不还是一样后面回写的id是错的吗

Comment From: wsliliang

@miemieYaho 如果是使用mybatis-plus的BaseMapper的insert方法和ServiceImpl中的saveBatch是不会出错的,如果前面没有set,中间set了,那前面的会用数据生成的,中间的会用自己set的,后面的又会用自动生成的。

因为insert injector中生成的sql是有if判断的,设置了id的记录最终生成的sql语句会有id列,没设置id的记录最终生成的sql没有id列,在mybatis的BatchExecutor的doUpdate方法中, 这些sql会被分成两部分:带id列的sql和不带id列的sql,在doFlushStatements方法是分别执行executeBatch,再使用Jdbc3KeyGenerator给实体类的主键属性赋值,所以我们判断如果实体类的ID属性没有值,那肯定是用数据库生成的值,如果有值,那肯定是用的开发者设置的值,所以就不用再赋值。

// BatchExecutor doUpdate
// 这里会将带id的不带id的分开,放到statementList中
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
      handler.parameterize(stmt);// fix Issues 322
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
 } else {
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);    // fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
 }
// BatchExecutor doFlushStatements
// 循环带id的statement和不带id的statement
for (int i = 0, n = statementList.size(); i < n; i++) {
       // 真正执行
       batchResult.setUpdateCounts(stmt.executeBatch());
       // 赋值主键属性
       jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
}