MyBatis-Plus 批量保存的操作方法
前言
在項(xiàng)目開發(fā)中,需要插入批量插入20多萬(wàn)條數(shù)據(jù),通過(guò)日志觀察,發(fā)現(xiàn)在調(diào)用MyBatis-Plus中的saveBatch()方法性能非常的差,本篇文章主要分享一下saveBatch()的原理以及使用的注意事項(xiàng)
原理
我們通過(guò)源碼的形式進(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++; } }); }
通過(guò)flushStatements()方法我們可以看到最終調(diào)用的是StatementImpl中的executeBatchInternal()方法。注意:代碼過(guò)長(zhǎng),下面方法做了刪減。
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(); } } }
我們?cè)倏聪耰nsert做了什么事情
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語(yǔ)句必須完全一致,包括表名和列 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ù)庫(kù)鏈接參數(shù)加上rewriteBatchedStatements=true
rewriteBatchedStatements參數(shù)需要保證5.1.13以上版本的驅(qū)動(dòng)才能實(shí)現(xiàn)高性能的批量插入
2: 根據(jù)doUpdate(ms,parameter). 完成SQL的拼裝的原理可以得出,如果批量插入的數(shù)據(jù),有些數(shù)據(jù)字段值為null,不會(huì)批量查詢,而是單獨(dú)拼裝一個(gè)SQL執(zhí)行。
例如:
public class Student { private String name; private String address; }
100個(gè)Student,其中 20個(gè)name=null,其中 50個(gè)address==null。通過(guò)日志我們看下這種不會(huì)批量插入。
到此這篇關(guān)于MyBatis-Plus 批量保存方法的文章就介紹到這了,更多相關(guān)MyBatis-Plus 批量保存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java智能問答圖靈機(jī)器人AI接口(聚合數(shù)據(jù))
這篇文章主要介紹了java智能問答圖靈機(jī)器人AI接口(聚合數(shù)據(jù)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02Java實(shí)現(xiàn)按權(quán)重隨機(jī)數(shù)
這篇文章主要介紹了Java實(shí)現(xiàn)按權(quán)重隨機(jī)數(shù),本文給出了提出問題、分析問題、解決問題三個(gè)步驟,需要的朋友可以參考下2015-04-04Java創(chuàng)建線程池為什么一定要用ThreadPoolExecutor
本文介紹了Java創(chuàng)建線程池為什么一定要用ThreadPoolExecutor,手動(dòng)方式使用ThreadPoolExecutor創(chuàng)建線程池和使用Executors執(zhí)行器自動(dòng)創(chuàng)建線程池,下文更多相關(guān)內(nèi)容需要的小伙伴可以參考一下2022-05-05Springboot-dubbo-fescar 阿里分布式事務(wù)的實(shí)現(xiàn)方法
這篇文章主要介紹了Springboot-dubbo-fescar 阿里分布式事務(wù)的實(shí)現(xiàn)方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03nacos中的配置使用@Value注解獲取不到值的原因及解決方案
這篇文章主要介紹了nacos中的配置使用@Value注解獲取不到值的原因分析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03