mybatis-plus使用上需要注意的问题

2021-08-20 15:05:04 浏览数 (1)

mybatis-plus使用上需要注意的问题

1.问题产生

之前,开发项目使用的是tk-mapper,当使用批量操作时,通常使用insertList就可以了。但是,最近的项目使用的是mybaits-plus,在使用批量操作saveBatch的使用,却遇到了一个问题,这个一开始让我以为我的数据出现了重复,但是仔细看,不是数据出现了重复,而是因为有一个字段相同,报唯一索引字段重复插入 Duplicate entry。

下面是我插入数据的sql和相对应的数据信息:

但是插入却报了错:

第一个感觉很诧异,批量插入操作,为啥还报了唯一索引异常了呢。

2.查看mybatis-plus封装代码

可以看到其批量操作的本质是一个for循环操作,注意参数里面出现了ignore:

代码语言:javascript复制
/**
 * 批量插入
 *
 * @param entityList ignore
 * @param batchSize  ignore
 * @return ignore
 */
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize) {
    String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE);
    int size = entityList.size();
    executeBatch(sqlSession -> {
        int i = 1;
        //遍历需要插入的数据列表,也即将数据想打包,然后执行批量操作
        for (T entity : entityList) {
            sqlSession.insert(sqlStatement, entity);
            if ((i % batchSize == 0) || i == size) {
                sqlSession.flushStatements();
            }
            i  ;
        }
    });
    return true;
}

同时还可以看到其插入操作底层却是更新操作。

代码语言:javascript复制
@Override
public int insert(String statement, Object parameter) {
  return update(statement, parameter);
}

可以看到mybatis的执行器执行的是更新方法:

代码语言:javascript复制
@Override
public int update(String statement, Object parameter) {
  try {
    dirty = true;
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.update(ms, wrapCollection(parameter));
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: "   e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

而当插入数据完成后,刷新语句,然后执行批量操作

代码语言:javascript复制
  /**
     * 执行批量操作
     *
     * @param fun fun
     * @since 3.3.0
     */
    protected void executeBatch(Consumer<SqlSession> fun) {
        Class<T> tClass = currentModelClass();
        SqlHelper.clearCache(tClass);
        SqlSessionFactory sqlSessionFactory = SqlHelper.sqlSessionFactory(tClass);
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        try {
            fun.accept(sqlSession);
            sqlSession.commit();
        } catch (Throwable t) {
            sqlSession.rollback();
            Throwable unwrapped = ExceptionUtil.unwrapThrowable(t);
            if (unwrapped instanceof RuntimeException) {
                MyBatisExceptionTranslator myBatisExceptionTranslator
                    = new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true);
                throw Objects.requireNonNull(myBatisExceptionTranslator.translateExceptionIfPossible((RuntimeException) unwrapped));
            }
            throw ExceptionUtils.mpe(unwrapped);
        } finally {
            sqlSession.close();
        }
    }
}

3.出现异常的地方

我的执行操作却走到了catch中:

也即在执行刷新操作就出现了错误。可以看到入参中是有数据的:

问题出现在刷新语句上。

4.问题的解决

在网上查到能够解决这个问题的两种方法:一种是在mysql的数据源中加入allowMultiQueries=true,还有一种结果在sql中使用igrone,我试了一下在其后加上allowMultiQueries=true,发现可以实现。

那为了解决mybatis-plus的批量插入操作可以使用ignore来避免批量插入失败的问题。

经过代码排查,以及批量update语句通过SQL工具直接执行均能成功,排除代码和sql语句问题,发现使用mybatis进行批量插入与更新时,必须在配置连接url时指定allowMultiQueries=true

但是发现我们的配置数据库配置中居然没有加:allowMultiQueries=true

代码语言:javascript复制
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai&allowMultiQueries=true

同时在网上还看到:

从 MyBatis3.3.1 版本开始,MyBatis开始支持批量新增回写主键值的功能,这个功能首先要求数据库主键值为自增类型,同时还要求该数据库提供的 JDBC 驱动可以支持返回批量插入的主键值(JDBC提供了接口,但并不是所有数据库都完美实现了该接口),因此到目前为止,可以完美支持该功能的仅有MySQL数据库。由于SQL Server数据库官方提供的 JDBC 只能返回最后一个插入数据的主键值,所以不能支持该功能。mybatis-plus的作者说可以使用 mp idWroker 完美解决。

同时在查资料的时候,发现低版本的mybatis-plus会出现批量更新insertBatch失败的问题。如果使用低版本出现这个问题,那更换成高版本的可以解决这个问题。

如果还不行的话,尝试在mybatis的xml文件中,使用sql的时候加上igrone.

0 人点赞