您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“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
1.創建sql注入器
/** * 自定義方法SQL注入器 * 【注意】這個類名,可以隨便命名 */ public class MyInjector extends DefaultSqlInjector { /** * 如果只需增加方法,保留MyBatis plus自帶方法, * 可以先獲取super.getMethodList(),再添加add */ @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; } }
2.注入容器
@Configuration @MapperScan("com.yida.mapper") public class MybatisPlusPageConfig { @Bean public MyInjector myInjector(){ return new MyInjector(); } }
3.定義通用mapper
/** * 公共mapper * 要實現批量新增/修改 繼承此類 * * @param <T> */ public interface CommonMapper<T> extends BaseMapper<T> { /** * 自定義批量插入 * 如果要自動填充,@Param(xx) xx參數名必須是 list/collection/array 3個的其中之一 */ int insertBatch(@Param("list") List<T> list); /** * 自定義批量更新,條件為主鍵 * 如果要自動填充,@Param(xx) xx參數名必須是 list/collection/array 3個的其中之一 */ int updateBatch(@Param("list") List<T> list); }
4.新增/修改
/** * 批量新增 */ @Slf4j public class InsertBatchMethod extends AbstractMethod { /** * insert into user(id, name, age) values (1, "a", 17), (2, "b", 18); <script> insert into user(id, name, age) values <foreach collection="list" item="item" index="index" open="(" separator="),(" close=")"> #{item.id}, #{item.name}, #{item.age} </foreach> </script> */ @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(); } }
/** * 批量更新方法實現,條件為主鍵,選擇性更新 */ @Slf4j public class UpdateBatchMethod extends AbstractMethod { /** * update user set name = "a", age = 17 where id = 1; * update user set name = "b", age = 18 where id = 2; <script> <foreach collection="list" item="item" separator=";"> update user <set> <if test="item.name != null and item.name != ''"> name = #{item.name,jdbcType=VARCHAR}, </if> <if test="item.age != null"> age = #{item.age,jdbcType=INTEGER}, </if> </set> where id = #{item.id,jdbcType=INTEGER} </foreach> </script> */ @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); } }
5.使用,將原有的繼承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 column data type. This is 0 for numeric types, the empty string ('') for string types, and the “zero” value for date and time types. INSERT INTO ... SELECT statements are handled the same way as multiple-row inserts because the server does not examine the result set from the SELECT to see whether it returns a 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的批量新增/批量更新問題怎么解決”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。