最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

mybatis-plus 批量插入效率低的問題解決

 更新時(shí)間:2026年01月26日 09:21:55   作者:沒錢的程序員  
本文揭示了Mybatis-Plus批量插入效率低的原因,并提供了解決方案,包括自定義SQL注入和重寫B(tài)aseMapper實(shí)現(xiàn)高效批量插入,具有一定的參考價(jià)值,感興趣的可以了解一下

背景

由于項(xiàng)目中需要大批量將數(shù)據(jù)插入數(shù)據(jù)庫,直接使用mybatis-plus中的批量插入方法,結(jié)果發(fā)現(xiàn)效率奇低無比,線上批量插入一千條數(shù)據(jù)居然花銷八九秒的時(shí)間。而我們的目標(biāo)是想要單次插入一萬條數(shù)據(jù),這樣的效率完全無法接受。

問題追蹤

mybatis-plus的源碼IService中是有單次批量插入的大小,默認(rèn)的DEFAULT_BATCH_SIZE=1000,可以看到很多批量方法里面都有設(shè)置;通過修改調(diào)用方法的入?yún)⒅担梢栽黾訂未闻坎迦氲臄?shù)據(jù),但實(shí)際發(fā)現(xiàn)并沒有什么提升。以下為mybatis-plus中service源碼:

public interface IService<T> {
 
    /**
     * 默認(rèn)批次提交數(shù)量
     */
    int DEFAULT_BATCH_SIZE = 1000;
 
    /**
     * 插入一條記錄(選擇字段,策略插入)
     *
     * @param entity 實(shí)體對象
     */
    default boolean save(T entity) {
        return SqlHelper.retBool(getBaseMapper().insert(entity));
    }
 
    /**
     * 插入(批量)
     *
     * @param entityList 實(shí)體對象集合
     */
    @Transactional(rollbackFor = Exception.class)
    default boolean saveBatch(Collection<T> entityList) {
        return saveBatch(entityList, DEFAULT_BATCH_SIZE);
    }
 
    /**
     * 插入(批量)
     *
     * @param entityList 實(shí)體對象集合
     * @param batchSize  插入批次數(shù)量
     */
    boolean saveBatch(Collection<T> entityList, int batchSize);
 
    /**
     * 批量修改插入
     *
     * @param entityList 實(shí)體對象集合
     */
    @Transactional(rollbackFor = Exception.class)
    default boolean saveOrUpdateBatch(Collection<T> entityList) {
        return saveOrUpdateBatch(entityList, DEFAULT_BATCH_SIZE);
    }
 
    /**
     * 批量修改插入
     *
     * @param entityList 實(shí)體對象集合
     * @param batchSize  每次的數(shù)量
     */
    boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
 
    ......
 
}

繼續(xù)接著上插入的源碼研究,發(fā)現(xiàn)底層在SqlHeper類中有個(gè)executeBatch的方法有點(diǎn)異常。該方法顯示 sqlSession.flushStatements()的調(diào)用居然是循環(huán)的。也就是說sql層面實(shí)際上是一堆insert語句再sqlSession中循環(huán)flush,而不是一個(gè)大insert一次flush操作完。這就是效率低的本質(zhì)原因。

/**
     * 執(zhí)行批量操作
     *
     * @param entityClass 實(shí)體類
     * @param log         日志對象
     * @param list        數(shù)據(jù)集合
     * @param batchSize   批次大小
     * @param consumer    consumer
     * @param <E>         T
     * @return 操作結(jié)果
     * @since 3.4.0
     */
    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) {
                consumer.accept(sqlSession, element);
                if ((i % batchSize == 0) || i == size) {
                    sqlSession.flushStatements();
                }
                i++;
            }
        });
    }

解決方案

因?yàn)樯鲜鲈?,考慮自己寫一個(gè)批量插入的sql語句,這是最簡單的。

但我們此處不采取此方法,而是直接重寫sql注入。以下DefaultSqlInjector為mybatis-plus默認(rèn)的sql注入實(shí)現(xiàn)類,繼承的是AbstractSqlInjector類。該默認(rèn)實(shí)現(xiàn)類中添加的Insert、Delete、DeleteByMap等等,實(shí)際上就是對應(yīng)Mapper中所調(diào)用的各種方法。

/**
 * SQL 默認(rèn)注入器
 *
 * @author hubin
 * @since 2018-04-10
 */
public class DefaultSqlInjector extends AbstractSqlInjector {
 
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        return Stream.of(
            new Insert(),
            new Delete(),
            new DeleteByMap(),
            new DeleteById(),
            new DeleteBatchByIds(),
            new Update(),
            new UpdateById(),
            new SelectById(),
            new SelectBatchByIds(),
            new SelectByMap(),
            new SelectOne(),
            new SelectCount(),
            new SelectMaps(),
            new SelectMapsPage(),
            new SelectObjs(),
            new SelectList(),
            new SelectPage()
        ).collect(toList());
    }
}

 我們需要在默認(rèn)實(shí)現(xiàn)的基礎(chǔ)上將額外的sql注入進(jìn)去,所以直接繼承默認(rèn)的實(shí)現(xiàn)類做改進(jìn)。以下為源碼:

/**
 * 重寫DefaultSqlInjector
 */
public class SqlInjectorPlus extends DefaultSqlInjector {
 
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        //繼承原有方法
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        //注入新方法
        methodList.add(new InsertBatchSomeColumn());
        return methodList;
    }
 
}
 
/**
 * 注入
 */
@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig {
 
    /**
     * 增強(qiáng)sql注入的Bean
     *
     * @return
     */
    @Bean
    public SqlInjectorPlus sqlInjectorPlus() {
        return new SqlInjectorPlus();
    }
}
 
 
/**
 * 重寫B(tài)aseMapper
 */
public interface BaseMapperPlus <T> extends BaseMapper<T> {
 
    /**
     * 高效率批量插入
     * entityList數(shù)量不能太大,否則存在丟包問題;
     * 單次entityList數(shù)量務(wù)必控制在一萬內(nèi),或者在service中再次封裝控制數(shù)量;
     * @param entityList 數(shù)據(jù)列表
     * @return 成功標(biāo)示
     */
    Integer insertBatchSomeColumn(Collection<T> entityList);
 
}

SqlInjectorPlus繼承 DefaultSqlInjector 進(jìn)行重寫,繼承原有增刪改查方法,加入新方法InsertBatchSomeColumn(該類為mybatis-plus源碼中就有的,但并未放開來使用)。用MybatisPlusConfig 將Bean注入到spring管理,最后再重寫一個(gè)BaseMapper并加入新方法。后續(xù)的mapper直接繼承BaseMapperPlus 就可以調(diào)用批量插入的insertBatchSomeColumn方法了。

InsertBatchSomeColumn類

該類為mybatis-plus源碼中就有的,但并未放開來使用。主要目的就是實(shí)現(xiàn)批量插入,生產(chǎn)的是一個(gè)單個(gè)大insert語句,注意如果數(shù)據(jù)量也不宜過大。因?yàn)閱未蝔lush一個(gè)大sql過去,如果數(shù)據(jù)量過大,產(chǎn)生丟包,則會(huì)導(dǎo)致該此批量插入失敗。最好再service中再封裝一次,做成分批循環(huán)調(diào)用。該類的源碼如下:

 
/**
 * 批量新增數(shù)據(jù),自選字段 insert
 * <p> 不同的數(shù)據(jù)庫支持度不一樣!!!  只在 mysql 下測試過!!!  只在 mysql 下測試過!!!  只在 mysql 下測試過!!! </p>
 * <p> 除了主鍵是 <strong> 數(shù)據(jù)庫自增的未測試 </strong> 外理論上都可以使用!!! </p>
 * <p> 如果你使用自增有報(bào)錯(cuò)或主鍵值無法回寫到entity,就不要跑來問為什么了,因?yàn)槲乙膊恢?!! </p>
 * <p>
 * 自己的通用 mapper 如下使用:
 * <pre>
 * int insertBatchSomeColumn(List<T> entityList);
 * </pre>
 * </p>
 *
 * <li> 注意: 這是自選字段 insert !!,如果個(gè)別字段在 entity 里為 null 但是數(shù)據(jù)庫中有配置默認(rèn)值, insert 后數(shù)據(jù)庫字段是為 null 而不是默認(rèn)值 </li>
 *
 * <p>
 * 常用的 {@link Predicate}:
 * </p>
 *
 * <li> 例1: t -> !t.isLogicDelete() , 表示不要邏輯刪除字段 </li>
 * <li> 例2: t -> !t.getProperty().equals("version") , 表示不要字段名為 version 的字段 </li>
 * <li> 例3: t -> t.getFieldFill() != FieldFill.UPDATE) , 表示不要填充策略為 UPDATE 的字段 </li>
 *
 * @author miemie
 * @since 2018-11-29
 */
@NoArgsConstructor
@AllArgsConstructor
public class InsertBatchSomeColumn extends AbstractMethod {
 
    /**
     * 字段篩選條件
     */
    @Setter
    @Accessors(chain = true)
    private Predicate<TableFieldInfo> predicate;
 
    @SuppressWarnings("Duplicates")
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        KeyGenerator keyGenerator = new NoKeyGenerator();
        SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
        List<TableFieldInfo> fieldList = tableInfo.getFieldList();
        String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(false) +
            this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
        String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
        String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(ENTITY_DOT, false) +
            this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY);
        insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
        String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", null, ENTITY, COMMA);
        String keyProperty = null;
        String keyColumn = null;
        // 表包含主鍵處理邏輯,如果不包含主鍵當(dāng)普通字段處理
        if (tableInfo.havePK()) {
            if (tableInfo.getIdType() == IdType.AUTO) {
                /* 自增主鍵 */
                keyGenerator = new Jdbc3KeyGenerator();
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            } else {
                if (null != tableInfo.getKeySequence()) {
                    keyGenerator = TableInfoHelper.genKeyGenerator(getMethod(sqlMethod), tableInfo, builderAssistant);
                    keyProperty = tableInfo.getKeyProperty();
                    keyColumn = tableInfo.getKeyColumn();
                }
            }
        }
        String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator, keyProperty, keyColumn);
    }
 
    @Override
    public String getMethod(SqlMethod sqlMethod) {
        // 自定義 mapper 方法名
        return "insertBatchSomeColumn";
    }
}

總結(jié):

mybatis-plus批量插入效率低的本質(zhì)原因是底層代碼中在sqlsession中循環(huán)flush的多條insert語句,因此改進(jìn)方案有兩個(gè):1.寫一個(gè)sql實(shí)現(xiàn)循環(huán)插入;2.重寫DefaultSqlInjector類,加入自帶的InsertBatchSomeColumn。

到此這篇關(guān)于mybatis-plus 批量插入效率低的問題解決的文章就介紹到這了,更多相關(guān)mybatis-plus 批量插入效率低內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringMVC @RequestBody自動(dòng)轉(zhuǎn)json Http415錯(cuò)誤的解決

    SpringMVC @RequestBody自動(dòng)轉(zhuǎn)json Http415錯(cuò)誤的解決

    這篇文章主要介紹了SpringMVC @RequestBody自動(dòng)轉(zhuǎn)json Http415錯(cuò)誤的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • springboot使用yml文件配置多環(huán)境方式(dev、test、prod)

    springboot使用yml文件配置多環(huán)境方式(dev、test、prod)

    這篇文章主要介紹了springboot使用yml文件配置多環(huán)境方式(dev、test、prod),具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • SpringBoot如何通過Map實(shí)現(xiàn)天然的策略模式

    SpringBoot如何通過Map實(shí)現(xiàn)天然的策略模式

    文章介紹了如何在Spring框架中使用@Resource注解和Map集合來實(shí)現(xiàn)策略模式,并詳細(xì)解釋了Spring如何處理集合類型的依賴注入,通過這種方式,可以在運(yùn)行時(shí)動(dòng)態(tài)地選擇算法的行為,使得代碼更加靈活和可擴(kuò)展
    2026-03-03
  • SpringBoot開發(fā)中的數(shù)據(jù)源詳解

    SpringBoot開發(fā)中的數(shù)據(jù)源詳解

    這篇文章主要介紹了SpringBoot開發(fā)中的數(shù)據(jù)源詳解,數(shù)據(jù)源(Data Source)顧名思義,數(shù)據(jù)的來源,是提供某種所需要數(shù)據(jù)的器件或原始媒體,在數(shù)據(jù)源中存儲(chǔ)了所有建立數(shù)據(jù)庫連接的信息,需要的朋友可以參考下
    2023-09-09
  • MyBatis連接數(shù)據(jù)庫配置的基本步驟和機(jī)制

    MyBatis連接數(shù)據(jù)庫配置的基本步驟和機(jī)制

    MyBatis 是一個(gè)流行的持久層框架,它通過使用XML或注解的方式將SQL語句、存儲(chǔ)過程和Java方法進(jìn)行綁定,從而避免了手寫大量的JDBC代碼和手動(dòng)設(shè)置參數(shù)與結(jié)果集,本文給大家介紹了MyBatis連接數(shù)據(jù)庫配置的基本步驟和機(jī)制,需要的朋友可以參考下
    2024-05-05
  • Java使用Spire.Doc實(shí)現(xiàn)Word轉(zhuǎn)PDF的完整方案

    Java使用Spire.Doc實(shí)現(xiàn)Word轉(zhuǎn)PDF的完整方案

    在OA系統(tǒng)開發(fā)、電子合同生成等場景中,Java開發(fā)者在處理文檔自動(dòng)化時(shí)最常遇到的痛點(diǎn):生成的Word文檔通過傳統(tǒng)POI轉(zhuǎn)換PDF時(shí)格式丟失,本文介紹的Spire.Doc for Java方案能保持原版格式,實(shí)現(xiàn)Word轉(zhuǎn)PDF,文中通過代碼講解的非常詳細(xì),需要的朋友可以參考下
    2025-08-08
  • java 生成xml并轉(zhuǎn)為字符串的方法

    java 生成xml并轉(zhuǎn)為字符串的方法

    今天小編就為大家分享一篇java 生成xml并轉(zhuǎn)為字符串的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07
  • Java中NIO的三大核心組件詳細(xì)解析

    Java中NIO的三大核心組件詳細(xì)解析

    這篇文章主要介紹了Java中NIO的三大核心組件詳細(xì)解析,NIO的Buffer類是一個(gè)抽象類,位于java.nio包中,提供了一組更加有效的方法,用來進(jìn)行寫入和讀取的交替訪問,本質(zhì)上是一個(gè)內(nèi)存塊,既可以寫入數(shù)據(jù),也可以從中讀取數(shù)據(jù),需要的朋友可以參考下
    2023-12-12
  • SpringBoot實(shí)現(xiàn)接口加密的五大技巧分享

    SpringBoot實(shí)現(xiàn)接口加密的五大技巧分享

    為了有效應(yīng)對接口安全面臨的挑戰(zhàn),接口加密作為一種至關(guān)重要的手段,能夠?qū)⒃紨?shù)據(jù)轉(zhuǎn)化為密文進(jìn)行傳輸,從而確保數(shù)據(jù)在傳輸過程中的機(jī)密性和完整性,本文將深入探討SpringBoot接口加密的最佳實(shí)踐,需要的朋友可以參考下
    2025-07-07
  • Java JTable 實(shí)現(xiàn)日歷的示例

    Java JTable 實(shí)現(xiàn)日歷的示例

    這篇文章主要介紹了Java JTable 實(shí)現(xiàn)日歷的示例,幫助大家更好的理解和學(xué)習(xí)Java jtable的使用方法,感興趣的朋友可以了解下
    2020-10-10

最新評論

车致| 黔西县| 教育| 黄浦区| 九龙坡区| 祥云县| 莲花县| 阆中市| 恭城| 宁德市| 靖边县| 罗田县| 正宁县| 双峰县| 灵武市| 方山县| 颍上县| 南京市| 云梦县| 西昌市| 绥江县| 丹巴县| 巴楚县| 凌海市| 德化县| 乌海市| 水富县| 儋州市| 漯河市| 天全县| 怀化市| 日照市| 乌拉特前旗| 繁昌县| 遂宁市| 婺源县| 长海县| 黄大仙区| 白银市| 林芝县| 吴江市|