mybatis-plus批量更新太慢該如何解決詳解
最近使用mybatis-plus的 saveOrUpdateBath 和saveBath接口執(zhí)行特別慢,數(shù)據(jù)量大時(shí)往往需要十幾分鐘,打開(kāi)日志查看原來(lái)批量操作也是循環(huán)單條數(shù)據(jù)插入的,那有沒(méi)有批量更新的辦法呢??
mybatis-plus 提供了一個(gè)自定義方法sql注入器DefaultSqlInjector我們可以通過(guò)繼DefaultSqlInjector來(lái)加入自定義的方法達(dá)到批量插入的效果。
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;
}
}同時(shí)我們需要繼承BaseMapper<T> 定義
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @description:自定義接口覆蓋BaseMapper,解決mybatis-plus 批量操作慢的問(wèn)題
* @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> {
}下面我們來(lái)定義批量插入的方法:
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;
// 表包含主鍵處理邏輯,如果不包含主鍵當(dāng)普通字段處理
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();
}
}
}
// 第三個(gè)參數(shù)必須和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();
}
}繼續(xù)定義批量插入更新的抽象方法
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);
// 第三個(gè)參數(shù)必須和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版本(本版本未測(cè)試 根據(jù)自己需求修改)
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:準(zhǔn)備屬性名
* @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版本(已測(cè)試完成,其中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:準(zhǔn)備屬性名
* @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();
}
}到此定義結(jié)束,下面開(kāi)始使用
@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數(shù)據(jù)量太大會(huì)出現(xiàn)異常
“Tried to send an out-of-range integer as a 2-byte value: 87923”
是因?yàn)閜g對(duì)于sql語(yǔ)句的參數(shù)數(shù)量是有限制的,最大為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個(gè)字節(jié)的integer,故其取值范圍為[-32768, 32767]。
這意味著sql語(yǔ)句的參數(shù)數(shù)量,即行數(shù)*列數(shù)之積必須小于等于32767.
比如,總共有17個(gè)字段,因?yàn)樽畲笫?2767,這樣最多允許32767/ 17 大約是1 927個(gè),所以要分批操作,或有能力的童鞋可以自己修改pg的驅(qū)動(dòng)呦
分批插入代碼如下:
/**
* @description:
* @author:
* @date: 2022/11/4 14:57
* @param: [list, fieldCount:列數(shù)]
* @return: void
**/
public void detachSaveOrUpdate_score(List<TScore> list, int fieldCount) {
int numberBatch = 32767; //每一次插入的最大數(shù)
//每一次插入的最大行數(shù) , 向下取整
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不能超過(guò)最大索引值
}
scoreMapper.pgInsertOrUpdateBatch(list.subList(v * i, end)); //插入數(shù)據(jù)庫(kù)
logger.info("更新一次~~~{}-{}", v * i, end);
}
}完成收工~~~
總結(jié)
到此這篇關(guān)于mybatis-plus批量更新太慢該如何解決的文章就介紹到這了,更多相關(guān)mybatis-plus批量更新太慢內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis中的PageHelper的執(zhí)行流程分析
這篇文章主要介紹了Mybatis的PageHelper執(zhí)行流程,本文給大家介紹介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02
Java?Zip壓縮之簡(jiǎn)化文件和文件夾的壓縮操作
這篇文章主要給大家介紹了關(guān)于Java?Zip壓縮之簡(jiǎn)化文件和文件夾的壓縮操作,Zip壓縮是一種常見(jiàn)的文件壓縮格式,它將多個(gè)文件和文件夾打包成一個(gè)以.zip為后綴的壓縮包,需要的朋友可以參考下2023-10-10
java中用float時(shí),數(shù)字后面加f,這樣是為什么你知道嗎
這篇文章主要介紹了java用float時(shí),數(shù)字后面加f,這樣是為什么你知道嗎?具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
基于OpenCv與JVM實(shí)現(xiàn)加載保存圖像功能(JAVA?圖像處理)
openCv有一個(gè)名imread的簡(jiǎn)單函數(shù),用于從文件中讀取圖像,本文給大家介紹JAVA?圖像處理基于OpenCv與JVM實(shí)現(xiàn)加載保存圖像功能,感興趣的朋友一起看看吧2022-01-01
idea不能自動(dòng)補(bǔ)全yml配置文件的原因分析
這篇文章主要介紹了idea不能自動(dòng)補(bǔ)全yml配置文件的原因,通過(guò)添加yml文件為配置文件能夠很快的解決,具體解決步驟跟隨小編一起通過(guò)本文學(xué)習(xí)下吧2021-06-06
Spring Boot Starter 自動(dòng)裝配原理全解析
Spring Boot Starter 的核心設(shè)計(jì)理念是 約定優(yōu)于配置,其核心實(shí)現(xiàn)基于 自動(dòng)配置(Auto-Configuration) 和 條件化注冊(cè)(Conditional Registration),這篇文章主要介紹了Spring Boot Starter 自動(dòng)裝配原理全解析,需要的朋友可以參考下2025-04-04
Spring Cloud Stream異常處理過(guò)程解析
這篇文章主要介紹了Spring Cloud Stream異常處理過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
Spring?Boot?3.4.3?基于?Spring?WebFlux?實(shí)現(xiàn)?SSE?功能(代碼示例)
Spring Boot 3.4.3 結(jié)合Spring WebFlux實(shí)現(xiàn)SSE 功能,為實(shí)時(shí)數(shù)據(jù)推送提供了優(yōu)雅的解決方案,通過(guò)本文的步驟,你可以快速搭建一個(gè)基于事件驅(qū)動(dòng)的后端服務(wù),滿足實(shí)時(shí)通知或監(jiān)控等需求,感興趣的朋友一起看看吧2025-04-04

