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.