<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
最近使用mybatis-plus的 saveOrUpdateBath 和saveBath介面執行特別慢,資料量大時往往需要十幾分鍾,開啟紀錄檔檢視原來批次操作也是迴圈單條資料插入的,那有沒有批次更新的辦法呢??
mybatis-plus 提供了一個自定義方法sql注入器DefaultSqlInjector我們可以通過繼DefaultSqlInjector來加入自定義的方法達到批次插入的效果。
import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; import org.springframework.stereotype.Component; import java.util.List; /** * @Description: 自定義方法SQL隱碼攻擊器 * @Title: CustomizedSqlInjector * @Package com.highgo.edu.common.batchOperation * @Author: * @Copyright * @CreateTime: 2022/11/3 16:21 */ @Component public class CustomizedSqlInjector extends DefaultSqlInjector { /** * 如果只需增加方法,保留mybatis plus自帶方法, * 可以先獲取super.getMethodList(),再新增add */ @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methodList = super.getMethodList(mapperClass); methodList.add(new InsertBatchMethod()); // methodList.add(new UpdateBatchMethod()); methodList.add(new MysqlInsertOrUpdateBath()); methodList.add(new PGInsertOrUpdateBath()); return methodList; } }
同時我們需要繼承BaseMapper<T> 定義
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Param; import java.util.List; /** * @description:自定義介面覆蓋BaseMapper,解決mybatis-plus 批次操作慢的問題 * @author: * @date: 2022/11/3 15:14 * @param: null * @return: **/ public interface RootMapper<T> extends BaseMapper<T> { /** * @description:批次插入 * @author: * @date: 2022/11/3 15:13 * @param: [list] * @return: int **/ int insertBatch(@Param("list") List<T> list); /** * @description:批次插入更新 * @author: * @date: 2022/11/3 15:14 * @param: [list] * @return: int **/ int mysqlInsertOrUpdateBatch(@Param("list") List<T> list); int pgInsertOrUpdateBatch(@Param("list") List<T> list); }
在需要使用批次更新插入的mapper上使用自定義的RootMapper
如下圖
import com.XX.edu.common.batchOperation.RootMapper; import com.XX.edu.exam.model.TScore; import org.springframework.stereotype.Repository; /** * @Entity com.XX.edu.exam.model.TScore */ @Repository public interface TScoreMapper extends RootMapper<TScore> { }
下面我們來定義批次插入的方法:
package com.XX.edu.common.batchOperation; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.core.enums.SqlMethod; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableInfo; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator; import org.apache.ibatis.executor.keygen.KeyGenerator; import org.apache.ibatis.executor.keygen.NoKeyGenerator; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @Description: 批次插入的方法 * @Title: InsertBatchMethod * @Package com.XX.edu.common.batchOperation * @Author: * @CreateTime: 2022/11/3 15:16 */ public class InsertBatchMethod extends AbstractMethod { Logger logger = LoggerFactory.getLogger(getClass()); @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); logger.debug("sqlResult----->{}", sqlResult); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass); KeyGenerator keyGenerator = new NoKeyGenerator(); SqlMethod sqlMethod = SqlMethod.INSERT_ONE; String keyProperty = null; String keyColumn = null; // 表包含主鍵處理邏輯,如果不包含主鍵當普通欄位處理 if (StringUtils.isNotEmpty(tableInfo.getKeyProperty())) { if (tableInfo.getIdType() == IdType.AUTO) { /* 自增主鍵 */ keyGenerator = new Jdbc3KeyGenerator(); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } else { if (null != tableInfo.getKeySequence()) { keyGenerator = TableInfoHelper.genKeyGenerator(sqlMethod.getMethod(),tableInfo, builderAssistant); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } } } // 第三個引數必須和RootMapper的自定義方法名一致 return this.addInsertMappedStatement(mapperClass, modelClass, "insertBatch", sqlSource, keyGenerator, keyProperty, keyColumn); } /** * @description: 拼接欄位值 * @author: * @date: 2022/11/3 15:20 * @param: [tableInfo] * @return: java.lang.String **/ 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 -> valueSql.append("#{item.").append(x.getProperty()).append("},")); valueSql.delete(valueSql.length() - 1, valueSql.length()); valueSql.append("</foreach>"); return valueSql.toString(); } /** * @description:拼接欄位 * @author: * @date: 2022/11/3 15:20 * @param: [tableInfo] * @return: java.lang.String **/ private String prepareFieldSql(TableInfo tableInfo) { StringBuilder fieldSql = new StringBuilder(); //fieldSql.append(tableInfo.getKeyColumn()).append(","); tableInfo.getFieldList().forEach(x -> { fieldSql.append(x.getColumn()).append(","); }); fieldSql.delete(fieldSql.length() - 1, fieldSql.length()); fieldSql.insert(0, "("); fieldSql.append(")"); return fieldSql.toString(); } }
繼續定義批次插入更新的抽象方法
package com.XX.edu.common.batchOperation; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableInfo; import org.apache.ibatis.executor.keygen.NoKeyGenerator; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlSource; /** * @Description: 批次插入更新 * @Title: InsertOrUpdateBath * @Package com.XX.edu.common.batchOperation * @Author: * @Copyright * @CreateTime: 2022/11/3 15:23 */ public abstract class InsertOrUpdateBathAbstract extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { final SqlSource sqlSource = prepareSqlSource(tableInfo, modelClass); // 第三個引數必須和RootMapper的自定義方法名一致 return this.addInsertMappedStatement(mapperClass, modelClass, prepareInsertOrUpdateBathName(), sqlSource, new NoKeyGenerator(), null, null); } protected abstract SqlSource prepareSqlSource(TableInfo tableInfo, Class<?> modelClass); protected abstract String prepareInsertOrUpdateBathName(); }
繼承上面的抽象類----mysql版本(本版本未測試 根據自己需求修改)
package com.XX.edu.common.batchOperation; import com.baomidou.mybatisplus.core.metadata.TableInfo; import org.apache.ibatis.mapping.SqlSource; import org.springframework.util.StringUtils; /** * @Description: 批次插入更新 * @Title: InsertOrUpdateBath * @Package com.XX.edu.common.batchOperation * @Author: * @Copyright * @CreateTime: 2022/11/3 15:23 */ public class MysqlInsertOrUpdateBath extends InsertOrUpdateBathAbstract { @Override protected SqlSource prepareSqlSource(TableInfo tableInfo, Class<?> modelClass) { final String sql = "<script>insert into %s %s values %s ON DUPLICATE KEY UPDATE %s</script>"; final String tableName = tableInfo.getTableName(); final String filedSql = prepareFieldSql(tableInfo); final String modelValuesSql = prepareModelValuesSql(tableInfo); final String duplicateKeySql = prepareDuplicateKeySql(tableInfo); final String sqlResult = String.format(sql, tableName, filedSql, modelValuesSql, filedSql, duplicateKeySql); //String.format(sql, tableName, filedSql, modelValuesSql, duplicateKeySql); //System.out.println("savaorupdatesqlsql="+sqlResult); return languageDriver.createSqlSource(configuration, sqlResult, modelClass); } @Override protected String prepareInsertOrUpdateBathName() { return "mysqlInsertOrUpdateBath"; } String prepareDuplicateKeySql(TableInfo tableInfo) { final StringBuilder duplicateKeySql = new StringBuilder(); if (!StringUtils.isEmpty(tableInfo.getKeyColumn())) { duplicateKeySql.append(tableInfo.getKeyColumn()).append("=values(").append(tableInfo.getKeyColumn()).append("),"); } tableInfo.getFieldList().forEach(x -> { duplicateKeySql.append(x.getColumn()) .append("=values(") .append(x.getColumn()) .append("),"); }); duplicateKeySql.delete(duplicateKeySql.length() - 1, duplicateKeySql.length()); return duplicateKeySql.toString(); } String prepareModelValuesSql(TableInfo tableInfo) { final StringBuilder valueSql = new StringBuilder(); valueSql.append("<foreach collection="list" item="item" index="index" open="(" separator="),(" close=")">"); if (!StringUtils.isEmpty(tableInfo.getKeyProperty())) { valueSql.append("#{item.").append(tableInfo.getKeyProperty()).append("},"); } tableInfo.getFieldList().forEach(x -> valueSql.append("#{item.").append(x.getProperty()).append("},")); valueSql.delete(valueSql.length() - 1, valueSql.length()); valueSql.append("</foreach>"); return valueSql.toString(); } /** * @description:準備屬性名 * @author: * @date: 2022/11/3 15:25 * @param: [tableInfo] * @return: java.lang.String **/ String prepareFieldSql(TableInfo tableInfo) { StringBuilder fieldSql = new StringBuilder(); fieldSql.append(tableInfo.getKeyColumn()).append(","); tableInfo.getFieldList().forEach(x -> { fieldSql.append(x.getColumn()).append(","); }); fieldSql.delete(fieldSql.length() - 1, fieldSql.length()); fieldSql.insert(0, "("); fieldSql.append(")"); return fieldSql.toString(); } }
繼承上面的抽象類----postgresql版本(已測試完成,其中id使用序列自增)
package com.XX.edu.common.batchOperation; import com.baomidou.mybatisplus.core.metadata.TableInfo; import org.apache.ibatis.mapping.SqlSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; /** * @Description: 批次插入更新 * @Title: InsertOrUpdateBath * @Package com.XX.edu.common.batchOperation * @Author: * @Copyright * @CreateTime: 2022/11/3 15:23 */ public class PGInsertOrUpdateBath extends InsertOrUpdateBathAbstract { Logger logger = LoggerFactory.getLogger(getClass()); @Override protected SqlSource prepareSqlSource(TableInfo tableInfo, Class<?> modelClass) { final String sql = "<script>insert into %s %s values %s on conflict (id) do update set %s </script>"; final String tableName = tableInfo.getTableName(); final String filedSql = prepareFieldSql(tableInfo); final String modelValuesSql = prepareModelValuesSql(tableInfo); final String duplicateKeySql = prepareDuplicateKeySql(tableInfo); final String sqlResult = String.format(sql, tableName, filedSql, modelValuesSql, duplicateKeySql); logger.info("sql=={}",sqlResult); return languageDriver.createSqlSource(configuration, sqlResult, modelClass); } @Override protected String prepareInsertOrUpdateBathName() { return "pgInsertOrUpdateBatch"; } private String prepareDuplicateKeySql(TableInfo tableInfo) { final StringBuilder duplicateKeySql = new StringBuilder(); if (!StringUtils.isEmpty(tableInfo.getKeyColumn())) { duplicateKeySql.append(tableInfo.getKeyColumn()).append("=excluded.").append(tableInfo.getKeyColumn()).append(","); } tableInfo.getFieldList().forEach(x -> { duplicateKeySql.append(x.getColumn()) .append("=excluded.") .append(x.getColumn()) .append(","); }); duplicateKeySql.delete(duplicateKeySql.length() - 1, duplicateKeySql.length()); return duplicateKeySql.toString(); } private String prepareModelValuesSql(TableInfo tableInfo) { final StringBuilder valueSql = new StringBuilder(); valueSql.append("<foreach collection="list" item="item" index="index" open="(" separator="),(" close=")">"); if (!StringUtils.isEmpty(tableInfo.getKeyProperty())) { valueSql.append("#{item.").append(tableInfo.getKeyProperty()).append("},"); } tableInfo.getFieldList().forEach(x -> valueSql.append("#{item.").append(x.getProperty()).append("},")); valueSql.delete(valueSql.length() - 1, valueSql.length()); valueSql.append("</foreach>"); return valueSql.toString(); } /** * @description:準備屬性名 * @author: * @date: 2022/11/3 15:25 * @param: [tableInfo] * @return: java.lang.String **/ private String prepareFieldSql(TableInfo tableInfo) { StringBuilder fieldSql = new StringBuilder(); if (!StringUtils.isEmpty(tableInfo.getKeyProperty())) { fieldSql.append(tableInfo.getKeyColumn()).append(","); } tableInfo.getFieldList().forEach(x -> { fieldSql.append(x.getColumn()).append(","); }); fieldSql.delete(fieldSql.length() - 1, fieldSql.length()); fieldSql.insert(0, "("); fieldSql.append(")"); return fieldSql.toString(); } }
到此定義結束,下面開始使用
@Service public class TNewExerciseServiceImpl extends ServiceImpl<TNewExerciseMapper, TNewExercise> implements TNewExerciseService { Logger logger = LoggerFactory.getLogger(getClass()); //引入mapper @Autowired TScoreMapper scoreMapper; //這樣就可以批次新增更新操作了 public void test(List<TScore> collect){ scoreMapper.pgInsertOrUpdateBatch(collect); } }
但是如果collect資料量太大會出現異常
“Tried to send an out-of-range integer as a 2-byte value: 87923”
是因為pg對於sql語句的引數數量是有限制的,最大為32767。
看pg原始碼
public void sendInteger2(int val) throws IOException { if (val >= -32768 && val <= 32767) { this.int2Buf[0] = (byte)(val >>> 8); this.int2Buf[1] = (byte)val; this.pgOutput.write(this.int2Buf); } else { throw new IOException("Tried to send an out-of-range integer as a 2-byte value: " + val); } }
從原始碼中可以看到pgsql使用2個位元組的integer,故其取值範圍為[-32768, 32767]。
這意味著sql語句的引數數量,即行數*列數之積必須小於等於32767.
比如,總共有17個欄位,因為最大是32767,這樣最多允許32767/ 17 大約是1 927個,所以要分批操作,或有能力的童鞋可以自己修改pg的驅動呦
分批插入程式碼如下:
/** * @description: * @author: * @date: 2022/11/4 14:57 * @param: [list, fieldCount:列數] * @return: void **/ public void detachSaveOrUpdate_score(List<TScore> list, int fieldCount) { int numberBatch = 32767; //每一次插入的最大數 //每一次插入的最大行數 , 向下取整 int v = ((Double) Math.floor(numberBatch / (fieldCount * 1.0))).intValue(); double number = list.size() * 1.0 / v; int n = ((Double) Math.ceil(number)).intValue(); //向上取整 for (int i = 0; i < n; i++) { int end = v * (i + 1); if (end > list.size()) { end = list.size(); //如果end不能超過最大索引值 } scoreMapper.pgInsertOrUpdateBatch(list.subList(v * i, end)); //插入資料庫 logger.info("更新一次~~~{}-{}", v * i, end); } }
完成收工~~~
總結
到此這篇關於mybatis-plus批次更新太慢該如何解決的文章就介紹到這了,更多相關mybatis-plus批次更新太慢內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45