本文小编为大家详细介绍“mybatis-plus的批量新增/批量更新问题怎么解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“mybatis-plus的批量新增/批量更新问题怎么解决”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一
本文小编为大家详细介绍“mybatis-plus的批量新增/批量更新问题怎么解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“mybatis-plus的批量新增/批量更新问题怎么解决”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。
现工作中有需求要进行批量新增和修改
实现了以下几种方式
代码中foreach insert/update
多线程foreach insert/update
mybatis xml中foreach
mybatis-plus扩展
第一种就不说了,重复的io连接与断开效率极低,性能很差,不考虑
第二种使用多线程进行批量插入/修改,时间会大大降低,但还会有频繁建立断开IO,性能不好
第三种其实就是在拼sql,但是不同业务要拼不同的sql,复用性很差
第四种本质也是拼sql,但是通过简单的配置就可以达到不同业务的复用
for(int i=0;i<insertList.size();i++){ offerMapper.insert(offerDO); }
更新同理
工作中也使用过多线程批量更新,新增同理
//定义线程池private static final Long KEEP_ALIVE_TIME = 60L;private static final int APS = Runtime.getRuntime().availableProcessors();private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( APS * 2, APS * 4, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingDeque<>(256), new ThreadFactoryBuilder().setNameFORMat("分拣出库-pool-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy());//使用try { taskExecute(list, yearList);} catch (Exception e) { log.error("分拣出库更新失败:{}", e);}// 处理单个任务数据(year是分库分表用的)private void taskExecute(List<SortinGoutboundProductDetailDO> list, List<Integer> yearList) throws Exception { if (CollectionUtils.isEmpty(list)) { return; } final CountDownLatch latch = new CountDownLatch(list.size()); for (SortingOutboundProductDetailDO data : list) { THREAD_POOL_EXECUTOR.submit(() -> { try { //更新从表 sortingOutboundProductDetailMapper.update(null, new LambdaUpdateWrapper<SortingOutboundProductDetailDO>() .eq(SortingOutboundProductDetailDO::getId, data.getId()) .in(SortingOutboundProductDetailDO::getYear, yearList) .set(SortingOutboundProductDetailDO::getOutboundNumber, data.getOutboundNumber()) ); } finally { if (latch != null) { latch.countDown(); } } }); } latch.await();}
批量新增
//不用关注里面的业务代码private int insert(OfferSaveRequest request){ List<OfferDO> insertOffer = request.getOfferList().stream().map(obj -> { OfferDO offerDO = new OfferDO(); offerDO.setId(IdWorker.getId()); offerDO.setFirstSubjectId(request.getFirstSubjectId()); offerDO.setWarehouseNum(request.getWarehouseNum()); offerDO.setExpressCompany(obj.getExpressCompany()); offerDO.setExpressCompanyName(obj.getExpressCompanyName()); offerDO.setArea(obj.getArea()); offerDO.setExpensesItemName(obj.getExpensesItemName()); offerDO.setUnit(obj.getUnit()); offerDO.setFees(obj.getFees()); offerDO.setDescription(obj.getDescription()); offerDO.setTransportType(generateTransportType(obj.getExpensesItemName())); offerDO.setCreateTime(new Date()); offerDO.setCreateUserId(1L); offerDO.setCreateUserName("管理员"); return offerDO; }).collect(Collectors.toList()); return offerMapper.batchInsert(insertOffer);}
xml
<insert id="batchInsert" parameterType="com.model.OfferDO"> INSERT INTO offer( id, first_subject_id, warehouse_num, express_company, express_company_name, area, expenses_item_name, unit, fees, description, create_time, create_user_id, create_user_name ) values <foreach collection="offerList" separator="," item="offer"> ( #{offer.id}, #{offer.firstSubjectId}, #{offer.warehouseNum}, #{offer.expressCompany}, #{offer.expressCompanyName}, #{offer.area}, #{offer.expensesItemName}, #{offer.unit}, #{offer.fees}, #{offer.description}, #{offer.createTime}, #{offer.createUserId}, #{offer.createUserName} ) </foreach></insert>
批量修改
//不用关注里面的业务代码List<OfferSaveRequest.Offer> updateList = request.getOfferList().stream() .filter(obj -> obj.getId() != null).collect(Collectors.toList());if (updateList.size() > 0) { List<OfferDO> updateOffer = updateList.stream().map(obj -> { OfferDO offerDO = new OfferDO(); offerDO.setId(obj.getId()); offerDO.setArea(obj.getArea()); offerDO.setFees(obj.getFees()); offerDO.setDescription(obj.getDescription()); offerDO.setUpdateTime(new Date()); offerDO.setUpdateUserId(1L); offerDO.setUpdateUserName("管理员"); return offerDO; }).collect(Collectors.toList()); offerMapper.batchUpdate(updateOffer);}
xml
<update id="batchUpdate" parameterType="com.model.OfferDO"> <foreach collection="offerList" item="offer" separator=";"> update offer set <if test="offer.area!=null and offer.area!=''"> area=#{offer.area}, </if> <if test="offer.fees!=null"> fees=#{offer.fees}, </if> <if test="offer.description!=null and offer.description!=''"> description=#{offer.description}, </if> update_time=#{offer.updateTime}, update_user_id=#{offer.updateUserId}, update_user_name=#{offer.updateUserName} where id = #{offer.id} </foreach></update>
批量修改还需要在配置文件中配置&allowMultiQueries=true
,否则报错
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&allowMultiQueries=true
创建sql注入器
public class MyInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { // 注意:此SQL注入器继承了DefaultSqlInjector(默认注入器),调用了DefaultSqlInjector的getMethodList方法,保留了mybatis-plus的自带方法 List<AbstractMethod> methodList = super.getMethodList(mapperClass); methodList.add(new InsertBatchMethod()); methodList.add(new UpdateBatchMethod()); return methodList; } }
注入容器
@Configuration@MapperScan("com.yida.mapper")public class MybatisPlusPageConfig { @Bean public MyInjector myInjector(){ return new MyInjector(); }}
定义通用mapper
public interface CommonMapper<T> extends BaseMapper<T> { int insertBatch(@Param("list") List<T> list); int updateBatch(@Param("list") List<T> list);}
新增/修改
@Slf4jpublic class InsertBatchMethod extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { final String sql = "<script>insert into %s %s values %s</script>"; final String fieldSql = prepareFieldSql(tableInfo); final String valueSql = prepareValuesSql(tableInfo); final String sqlResult = String.format(sql, tableInfo.getTableName(), fieldSql, valueSql); log.debug("sqlResult----->{}", sqlResult); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass); // 第三个参数必须和RootMapper的自定义方法名一致 return this.addInsertMappedStatement(mapperClass, modelClass, "insertBatch", sqlSource, new NoKeyGenerator(), null, null); } private String prepareFieldSql(TableInfo tableInfo) { StringBuilder fieldSql = new StringBuilder(); fieldSql.append(tableInfo.geTKEyColumn()).append(","); tableInfo.getFieldList().forEach(x -> { //新增时修改字段不填充 if (!("update_time".equals(x.getColumn())) &&!("update_user_id".equals(x.getColumn())) &&!("update_user_name".equals(x.getColumn()))){ fieldSql.append(x.getColumn()).append(","); } }); fieldSql.delete(fieldSql.length() - 1, fieldSql.length()); fieldSql.insert(0, "("); fieldSql.append(")"); return fieldSql.toString(); } private String prepareValuesSql(TableInfo tableInfo) { final StringBuilder valueSql = new StringBuilder(); valueSql.append("<foreach collection=\"list\" item=\"item\" index=\"index\" open=\"(\" separator=\"),(\" close=\")\">"); valueSql.append("#{item.").append(tableInfo.getKeyProperty()).append("},"); tableInfo.getFieldList().forEach(x -> { if (!("updateTime".equals(x.getProperty())) &&!("updateUserId".equals(x.getProperty())) &&!("updateUserName".equals(x.getProperty()))){ valueSql.append("#{item.").append(x.getProperty()).append("},"); } }); valueSql.delete(valueSql.length() - 1, valueSql.length()); valueSql.append("</foreach>"); return valueSql.toString(); }}
@Slf4jpublic class UpdateBatchMethod extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { String sql = "<script>\n<foreach collection=\"list\" item=\"item\" separator=\";\">\nupdate %s %s where %s=#{%s} %s\n</foreach>\n</script>"; String additional = tableInfo.isWithVersion() ? tableInfo.getVersionFieldInfo().getVersionOli("item", "item.") : "" + tableInfo.getLogicDeleteSql(true, true); String setSql = sqlSet(tableInfo.isWithLogicDelete(), false, tableInfo, false, "item", "item."); String sqlResult = String.format(sql, tableInfo.getTableName(), setSql, tableInfo.getKeyColumn(), "item." + tableInfo.getKeyProperty(), additional); log.debug("sqlResult----->{}", sqlResult); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass); // 第三个参数必须和RootMapper的自定义方法名一致 return this.addUpdateMappedStatement(mapperClass, modelClass, "updateBatch", sqlSource); } }
使用,将原有的继承BaseMapper的方法,改写为继承CommonMapper,后续批量操作,直接使用新增的两个方法进行处理即可。
public interface OfferMapper extends CommonMapper<OfferDO> {}
新增:offerMapper.insertBatch(insertOffer)
更新:offerMapper.updateBatch(updateOffer)
在实际使用中发现一个问题,这个批量插入是在项目启动后就进行拼接好的sql,然后调用的时候,进行值得替换,例
<script>INSERT INTO express (id,express_name,express_code,state,create_time,create_user_id,create_user_name) VALUES <foreach collection="list" item="et" separator=",">(#{id},#{expressName},#{expressCode},#{state},#{createTime},#{createUserId},#{createUserName})</foreach></script>
发现是全量新增,这样也就产生了一个问题,当只想新增一部分数据,剩下的一部分数据更新进去时,有时
会有问题,这取决于你数据库中字段设置是可以为null还是不可以为null。
当数据库中字段设置为not null,而新增的时候传一个null,就会触发数据库的not null校验,报错
然后就查资料,改代码,测试,然后官网上是这样说的
参考:https://blog.csdn.net/weixin_45505313/article/details/121574166
Inserting NULL into a column that has been declared NOT NULL. For multiple-row INSERT statements or INSERT INTO ... SELECT statements, the column is set to the implicit default value for the columndata type. This is 0 for numeric types, the empty string ('') for string types, and the “zero” valuefor date and time types. INSERT INTO ... SELECT statements are handled the same way as multiple-rowinserts because the server does not examine the result set from the SELECT to see whether it returnsa single row. (For a single-row INSERT, no warning occurs when NULL is inserted into a NOT NULL column.Instead, the statement fails with an error.)在声明为NOT NULL的列中插入NULL。对于多行INSERT语句或插入…SELECT语句时,该列被设置为该列的隐式默认值数据类型。数值类型为0,字符串类型为空字符串("),值为" 0 "用于日期和时间类型。插入…SELECT语句的处理方式与多行语句相同插入,因为服务器不检查SELECT的结果集,看它是否返回单行。(对于单行INSERT,当NULL插入到NOT NULL列时,不会出现警告。相反,语句失败并报错。)
也就是说Mysql允许批量插入时,向not null字段插入null值,mysql会给其赋一个隐藏值
但是在我实测下发现并不行,然后又开始查资料,被我发现了这个
那么我就查了一下我的数据库模式
select @@sql_mode;结果:ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
可以看出我的数据库模式为严格模式,怪不得官网说可以插入null,而我的代码一直报错,排坑之旅任重道远
解决方案:
1.关闭数据库的严格模式(公司的数据库没有权限,这个直接pass掉)
2.手动拼批量插入的sql,拼成如下样子,然后一次请求执行,这样只进行了一次数据库连接,也可以实现批量插入的效果,但是不知道这种多个INSERT语句与单个INSERT和多个VALUES的性能怎么样
INSERT INTO `oss` VALUES (1, -1, '', '测试用文件.docx', '', '', 0, '', '2022-12-08 16:21:33', 1, '系统管理员', NULL, -1, '');INSERT INTO `oss` VALUES (2, -1, '', '测试用文件.docx', '', '', 0, '', '2022-12-08 16:32:32', 1, '系统管理员', NULL, -1, '');INSERT INTO `oss` VALUES (3, -1, '', '测试用文件.docx', '', '', 0, '', '2022-12-08 16:33:17', 1, '系统管理员', NULL, -1, '');INSERT INTO `oss` VALUES (4, -1, '', '测试用文件.docx', '', '', 0, '', '2022-12-08 16:44:30', 1, '系统管理员', NULL, -1, '');INSERT INTO `oss` VALUES (5, -1, '', '测试用文件.docx', '', '', 0, '', '2022-12-08 16:45:28', 1, '系统管理员', NULL, -1, '');INSERT INTO `oss` VALUES (6, -1, '', '合同导入测试.doc','', '', 0, '', '2022-12-08 16:47:03', 1, '系统管理员', NULL, -1, '');INSERT INTO `oss` VALUES (7, -1, '', '合同导入测试.doc','', '', 0, '', '2022-12-08 16:48:03', 1, '系统管理员', NULL, -1, '');INSERT INTO `oss` VALUES (8, -1, '', '测试用文件.docx', '', '', 1, '', '2022-12-08 16:49:35', 1, '系统管理员', NULL, -1, '');INSERT INTO `oss` VALUES (9, -1, '', '新建文本文档.doc','', '', 0, '', '2022-12-08 17:12:36', 1, '系统管理员', NULL, -1, '');
读到这里,这篇“mybatis-plus的批量新增/批量更新问题怎么解决”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网精选频道。
--结束END--
本文标题: mybatis-plus的批量新增/批量更新问题怎么解决
本文链接: https://www.lsjlt.com/news/356581.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-04-29
2024-04-29
2024-04-29
2024-04-29
2024-04-29
2024-04-29
2024-04-29
2024-04-29
2024-04-29
2024-04-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0