MyBatis-Plus 批量保存的操作方法
前言
在項(xiàng)目開發(fā)中,需要插入批量插入20多萬條數(shù)據(jù),通過日志觀察,發(fā)現(xiàn)在調(diào)用MyBatis-Plus中的saveBatch()方法性能非常的差,本篇文章主要分享一下saveBatch()的原理以及使用的注意事項(xiàng)
原理
我們通過源碼的形式進(jìn)行解析saveBatch()方法的原理
@Transactional(rollbackFor = Exception.class)
default boolean saveBatch(Collection<T> entityList) {
//DEFAULT_BATCH_SIZE 默認(rèn)是1000
return saveBatch(entityList, DEFAULT_BATCH_SIZE);
} @Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize) {
String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
//分批執(zhí)行SQL
return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
}我們看下saveBatch是怎么批量執(zhí)行的
public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, sqlSession -> {
int size = list.size();
int i = 1;
for (E element : list) {
//數(shù)據(jù)最終保存在StatementImpl.batchArgs中,用于批量保存
consumer.accept(sqlSession, element);
if ((i % batchSize == 0) || i == size) {
//批量保存StatementImpl.batchArgs中數(shù)據(jù)
sqlSession.flushStatements();
}
i++;
}
});
}通過flushStatements()方法我們可以看到最終調(diào)用的是StatementImpl中的executeBatchInternal()方法。注意:代碼過長,下面方法做了刪減。
protected long[] executeBatchInternal() throws SQLException {
synchronized (checkClosed().getConnectionMutex()) {
if (this.connection.isReadOnly()) {
throw new SQLException(Messages.getString("PreparedStatement.25") + Messages.getString("PreparedStatement.26"),
MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT);
}
if (this.query.getBatchedArgs() == null || this.query.getBatchedArgs().size() == 0) {
return new long[0];
}
// we timeout the entire batch, not individual statements
int batchTimeout = getTimeoutInMillis();
setTimeoutInMillis(0);
resetCancelledState();
try {
statementBegins();
clearWarnings();
// 如果配置rewriteBatchedStatements 開啟多SQL執(zhí)行
if (!this.batchHasPlainStatements && this.rewriteBatchedStatements.getValue()) {
if (getQueryInfo().isRewritableWithMultiValuesClause()) {
return executeBatchWithMultiValuesClause(batchTimeout);
}
if (!this.batchHasPlainStatements && this.query.getBatchedArgs() != null
&& this.query.getBatchedArgs().size() > 3 /* cost of option setting rt-wise */) {
return executePreparedBatchAsMultiStatement(batchTimeout);
}
}
return executeBatchSerially(batchTimeout);
} finally {
this.query.getStatementExecuting().set(false);
clearBatch();
}
}
}我們再看下insert做了什么事情
public int insert(String statement, Object parameter) {
return update(statement, parameter);
} 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();
}
} public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}重點(diǎn)方法在doUpdate(ms,parameter). 完成SQL的拼裝
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
// 數(shù)據(jù)的SQL語句必須完全一致,包括表名和列
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
handler.parameterize(stmt);// fix Issues 322
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt); // fix Issues 322
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}以上就是saveBatch的原理。
總結(jié)
1: 想要批量執(zhí)行操作 數(shù)據(jù)庫鏈接參數(shù)加上rewriteBatchedStatements=true
rewriteBatchedStatements參數(shù)需要保證5.1.13以上版本的驅(qū)動才能實(shí)現(xiàn)高性能的批量插入
2: 根據(jù)doUpdate(ms,parameter). 完成SQL的拼裝的原理可以得出,如果批量插入的數(shù)據(jù),有些數(shù)據(jù)字段值為null,不會批量查詢,而是單獨(dú)拼裝一個SQL執(zhí)行。
例如:
public class Student {
private String name;
private String address;
}100個Student,其中 20個name=null,其中 50個address==null。通過日志我們看下這種不會批量插入。
到此這篇關(guān)于MyBatis-Plus 批量保存方法的文章就介紹到這了,更多相關(guān)MyBatis-Plus 批量保存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java智能問答圖靈機(jī)器人AI接口(聚合數(shù)據(jù))
這篇文章主要介紹了java智能問答圖靈機(jī)器人AI接口(聚合數(shù)據(jù)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Java實(shí)現(xiàn)按權(quán)重隨機(jī)數(shù)
這篇文章主要介紹了Java實(shí)現(xiàn)按權(quán)重隨機(jī)數(shù),本文給出了提出問題、分析問題、解決問題三個步驟,需要的朋友可以參考下2015-04-04
Java創(chuàng)建線程池為什么一定要用ThreadPoolExecutor
本文介紹了Java創(chuàng)建線程池為什么一定要用ThreadPoolExecutor,手動方式使用ThreadPoolExecutor創(chuàng)建線程池和使用Executors執(zhí)行器自動創(chuàng)建線程池,下文更多相關(guān)內(nèi)容需要的小伙伴可以參考一下2022-05-05
Springboot-dubbo-fescar 阿里分布式事務(wù)的實(shí)現(xiàn)方法
這篇文章主要介紹了Springboot-dubbo-fescar 阿里分布式事務(wù)的實(shí)現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-03-03
nacos中的配置使用@Value注解獲取不到值的原因及解決方案
這篇文章主要介紹了nacos中的配置使用@Value注解獲取不到值的原因分析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03

