欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Mybatis-Plus saveBatch()批量保存失效的解決

 更新時(shí)間:2023年01月13日 11:07:40   作者:凌兮~  
本文主要介紹了Mybatis-Plus saveBatch()批量保存失效的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

問(wèn)題

在使用IService.savebatch方法批量插入數(shù)據(jù)時(shí),觀察控制臺(tái)打印的Sql發(fā)現(xiàn)并沒(méi)有像預(yù)想的一樣,而是以逐條方式進(jìn)行插入,插1000條數(shù)據(jù)就得10s多,正常假如批量插入應(yīng)該是一條語(yǔ)句:

insert table (field1, field2) values (val1, val2), (val3, val4), (val5, val6), ... ;

而我的是這樣:

insert table (field1, field2) values (val1, val2);
insert table (field1, field2) values (val3, val4);
...

問(wèn)題環(huán)境

  • jdk 1.8
  • spring-boot-starter 2.1.1.RELEASE
  • mybatis-plus 3.4.1
  • mysql-connector-java 8.0.13

排查過(guò)程

先是網(wǎng)上搜索有沒(méi)有類(lèi)似的經(jīng)驗(yàn),看到最多的是:在JDBC連接串最后添加參數(shù)rewriteBatchedStatements=true,可以大大增加批量插入的效率,加上了發(fā)現(xiàn)還是一條一條插,然后又搜索為什么這個(gè)參數(shù)沒(méi)用,有說(shuō)數(shù)據(jù)條數(shù)要>3,這個(gè)我肯定滿(mǎn)足,有說(shuō)JDBC驅(qū)動(dòng)版本問(wèn)題的,都試了沒(méi)用。
多方查詢(xún)無(wú)果,決定從源碼入手,一步一步跟進(jìn)看這個(gè)saveBatch到底怎么實(shí)現(xiàn)的,在哪一步出了問(wèn)題。

1.ServiceImpl.java

? ? /**
? ? ?* 批量插入
? ? ?*
? ? ?* @param entityList ignore
? ? ?* @param batchSize ?ignore
? ? ?* @return ignore
? ? ?*/
? ? @Transactional(rollbackFor = Exception.class)
? ? @Override
? ? public boolean saveBatch(Collection<T> entityList, int batchSize) {
? ? ? ? String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
? ? ? ? return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
? ? }

入口函數(shù),沒(méi)什么好說(shuō)的,重點(diǎn)看這個(gè)executeBatch

2. SqlHelper.java

? ? /**
? ? ?* 執(zhí)行批量操作
? ? ?*
? ? ?* @param entityClass 實(shí)體類(lèi)
? ? ?* @param log ? ? ? ? 日志對(duì)象
? ? ?* @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++;
? ? ? ? ? ? }
? ? ? ? });
? ? }

?/**
? ? ?* 執(zhí)行批量操作
? ? ?*
? ? ?* @param entityClass 實(shí)體
? ? ?* @param log ? ? ? ? 日志對(duì)象
? ? ?* @param consumer ? ?consumer
? ? ?* @return 操作結(jié)果
? ? ?* @since 3.4.0
? ? ?*/
? ? public static boolean executeBatch(Class<?> entityClass, Log log, Consumer<SqlSession> consumer) {
? ? ? ? SqlSessionFactory sqlSessionFactory = sqlSessionFactory(entityClass);
? ? ? ? SqlSessionHolder sqlSessionHolder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sqlSessionFactory);
? ? ? ? boolean transaction = TransactionSynchronizationManager.isSynchronizationActive();
? ? ? ? if (sqlSessionHolder != null) {
? ? ? ? ? ? SqlSession sqlSession = sqlSessionHolder.getSqlSession();
? ? ? ? ? ? //原生無(wú)法支持執(zhí)行器切換,當(dāng)存在批量操作時(shí),會(huì)嵌套兩個(gè)session的,優(yōu)先commit上一個(gè)session
? ? ? ? ? ? //按道理來(lái)說(shuō),這里的值應(yīng)該一直為false。
? ? ? ? ? ? sqlSession.commit(!transaction);
? ? ? ? }
? ? ? ? SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
? ? ? ? if (!transaction) {
? ? ? ? ? ? log.warn("SqlSession [" + sqlSession + "] was not registered for synchronization because DataSource is not transactional");
? ? ? ? }
? ? ? ? try {
? ? ? ? ? ? consumer.accept(sqlSession);
? ? ? ? ? ? //非事物情況下,強(qiáng)制commit。
? ? ? ? ? ? sqlSession.commit(!transaction);
? ? ? ? ? ? return true;
? ? ? ? } catch (Throwable t) {
? ? ? ? ? ? sqlSession.rollback();
? ? ? ? ? ? Throwable unwrapped = ExceptionUtil.unwrapThrowable(t);
? ? ? ? ? ? if (unwrapped instanceof RuntimeException) {
? ? ? ? ? ? ? ? MyBatisExceptionTranslator myBatisExceptionTranslator
? ? ? ? ? ? ? ? ? ? = new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true);
? ? ? ? ? ? ? ? throw Objects.requireNonNull(myBatisExceptionTranslator.translateExceptionIfPossible((RuntimeException) unwrapped));
? ? ? ? ? ? }
? ? ? ? ? ? throw ExceptionUtils.mpe(unwrapped);
? ? ? ? } finally {
? ? ? ? ? ? sqlSession.close();
? ? ? ? }
? ? }

打斷點(diǎn)發(fā)現(xiàn),每經(jīng)過(guò)一次consumer.accept(sqlSession),就打印一行insert語(yǔ)句出來(lái),看看里面搞了什么鬼

3. MybatisBatchExecutor.java

@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;
? ? ? ? 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());
? ? ? ? ? ? if (stmt == null) {
? ? ? ? ? ? ? ? return 0;
? ? ? ? ? ? }
? ? ? ? ? ? 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;
? ? }

一頓Step Into后進(jìn)入了這個(gè)doUpdate方法,看了一下,if體內(nèi)的應(yīng)該就是批量拼接sql的關(guān)鍵,走了幾個(gè)循環(huán)發(fā)現(xiàn)我的代碼都是從else體里走了,也就拆成了一條一條的插入語(yǔ)句,那他為什么不進(jìn)if呢,看了下判斷條件,每次進(jìn)來(lái)。statement都是一個(gè),那問(wèn)題就出在sql.equals(currentSql) 上面,我比對(duì)了下第二個(gè)實(shí)體的sql和第一個(gè)實(shí)體的sql,很快就發(fā)現(xiàn)了問(wèn)題,他們竟然不!一!樣!。
原因是在拼接insert語(yǔ)句時(shí),如果實(shí)體的某個(gè)屬性值為空,那他將不參與拼接,所以如果你的數(shù)據(jù)null值比較多且比較隨機(jī)的分布在各個(gè)屬性上,那生成出來(lái)的sql就會(huì)不一樣,也就沒(méi)法走批處理邏輯了。

為了驗(yàn)證這個(gè)發(fā)現(xiàn),我寫(xiě)了兩段測(cè)試代碼比對(duì):

a. list新增三個(gè)實(shí)體,每個(gè)實(shí)體在不同的屬性上設(shè)置空值

?? ?@Autowired
? ? private IBPModelService modelService;

?? ?@PostMapping("/save")
? ? @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
? ? public R testSaveBatch() {
? ? ? ? BPModel model_1 = new BPModel();
? ? ? ? model_1.setModelName("模型1");
? ? ? ? BPModel model_2 = new BPModel();
? ? ? ? model_2.setContent("模型2 content");
? ? ? ? BPModel model_3 = new BPModel();
? ? ? ? model_3.setModelDesc("模型3 desc");
? ? ? ? List<BPModel> list = new ArrayList<>();
? ? ? ? list.add(model_1);
? ? ? ? list.add(model_2);
? ? ? ? list.add(model_3);
? ? ? ? modelService.saveBatch(list);
? ? ? ? return R.ok();
? ? }

打印結(jié)果(三個(gè)語(yǔ)句):

JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@75dbdb41] will be managed by Spring
==>  Preparing: INSERT INTO BP_MODEL ( model_name ) VALUES ( ? )
==> Parameters: 模型1(String)
==>  Preparing: INSERT INTO BP_MODEL ( content ) VALUES ( ? )
==> Parameters: 模型2 content(String)
==>  Preparing: INSERT INTO BP_MODEL ( model_desc ) VALUES ( ? )
==> Parameters: 模型3 desc(String)

b. 還是生成三個(gè)實(shí)體,但是在相同屬性上設(shè)置空值,保證數(shù)據(jù)格式一致性

?@PostMapping("/save")
?@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
?public R testSaveBatch() {
? ? ? ? BPModel model_1 = new BPModel();
? ? ? ? model_1.setModelName("模型1");
? ? ? ? BPModel model_2 = new BPModel();
? ? ? ? model_2.setModelName("模型2");
? ? ? ? BPModel model_3 = new BPModel();
? ? ? ? model_3.setModelName("模型3");
? ? ? ? List<BPModel> list = new ArrayList<>();
? ? ? ? list.add(model_1);
? ? ? ? list.add(model_2);
? ? ? ? list.add(model_3);
? ? ? ? modelService.saveBatch(list);
? ? ? ? return R.ok();
?}

打印結(jié)果(一個(gè)語(yǔ)句):

JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@6e4b5fc7] will be managed by Spring
==>  Preparing: INSERT INTO BP_MODEL ( model_name ) VALUES ( ? )
==> Parameters: 模型1(String)
==> Parameters: 模型2(String)
==> Parameters: 模型3(String)

 果然,驗(yàn)證結(jié)論正確,實(shí)體屬性為null時(shí),會(huì)影響生成的插入sql,進(jìn)而影響批量保存邏輯。

解決方案

定位到了問(wèn)題,那就也便于解決了,問(wèn)題原因是生成插入sql時(shí),對(duì)null值的處理策略造成的,查閱mybatis-plus官方文檔發(fā)現(xiàn),有一個(gè)配置項(xiàng)可以解決這個(gè)問(wèn)題:

insertStrategy
類(lèi)型:com.baomidou.mybatisplus.annotation.FieldStrategy
默認(rèn)值:NOT_NULL
字段驗(yàn)證策略之 insert,在 insert 的時(shí)候的字段驗(yàn)證策略

 默認(rèn)為NOT_NULL就是導(dǎo)致問(wèn)題的關(guān)鍵,改成IGNORED就好了

再查資料發(fā)現(xiàn),在@TableField注解內(nèi)也可局部制定insertStrategy屬性, 那解決方案就比較多樣化了:

全局配置insertStrategy為IGNORED

# mybatis 全局配置
mybatis-plus:
? mapper-locations: classpath:mapper/*.xml
? global-config:
? ? db-config:
? ? ? id-type: auto
? ? ? insert-strategy: ignored
? configuration:
? ? map-underscore-to-camel-case: true
? ? call-setters-on-nulls: true

 為可能受影響的屬性添加注解

 @TableField(insertStrategy = FieldStrategy.IGNORED)
 private String content;

不管他那套,自己重寫(xiě)個(gè)批量保存方法,自己寫(xiě)xml拼接sql,簡(jiǎn)單粗暴(小心sql超出最大長(zhǎng)度)

  <insert id="insertBatch" parameterType="java.util.List">
        insert into table_name (id,code,name,content) VALUES
        <foreach collection ="list" item="entity" index= "index" separator =",">
            (
            #{entity.id}, #{entity.code}, #{entity.name}, #{entity.content}
            )
        </foreach>
</insert>

到此這篇關(guān)于Mybatis-Plus saveBatch()批量保存失效的解決的文章就介紹到這了,更多相關(guān)Mybatis-Plus saveBatch()批量保存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java中的靜態(tài)內(nèi)部類(lèi)詳解及代碼示例

    Java中的靜態(tài)內(nèi)部類(lèi)詳解及代碼示例

    這篇文章主要介紹了Java中的靜態(tài)內(nèi)部類(lèi)詳解及代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-10-10
  • Java開(kāi)發(fā)支付寶PC支付完整版

    Java開(kāi)發(fā)支付寶PC支付完整版

    這篇文章主要介紹了Java開(kāi)發(fā)支付寶PC支付完整版,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • 淺析Java模板方法的一種使用方式

    淺析Java模板方法的一種使用方式

    模板方法說(shuō)白了就是將一段代碼模板化,將通用的代碼段抽取出來(lái),并提供一些自定義的接口去定制的特定位置的某些業(yè)務(wù)功能。本文主要來(lái)和大家聊聊它的一種使用方式,希望對(duì)大家有所幫助
    2023-02-02
  • Mybatis常用分頁(yè)插件實(shí)現(xiàn)快速分頁(yè)處理技巧

    Mybatis常用分頁(yè)插件實(shí)現(xiàn)快速分頁(yè)處理技巧

    這篇文章主要介紹了Mybatis常用分頁(yè)插件實(shí)現(xiàn)快速分頁(yè)處理的方法。非常不錯(cuò)具有參考借鑒價(jià)值,感興趣的朋友一起看看
    2016-10-10
  • 基于Java的Scoket編程

    基于Java的Scoket編程

    本文詳細(xì)講解了基于Java的Scoket編程,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-12-12
  • 分享7款開(kāi)源Java反編譯工具

    分享7款開(kāi)源Java反編譯工具

    今天我們要來(lái)分享一些關(guān)于Java的反編譯工具,反編譯聽(tīng)起來(lái)是一個(gè)非常高上大的技術(shù)詞匯,通俗的說(shuō),反編譯是一個(gè)對(duì)目標(biāo)可執(zhí)行程序進(jìn)行逆向分析,從而得到原始代碼的過(guò)程。尤其是像.NET、Java這樣的運(yùn)行在虛擬機(jī)上的編程語(yǔ)言,更容易進(jìn)行反編譯得到源代碼
    2014-09-09
  • 基于java springboot + mybatis實(shí)現(xiàn)電影售票管理系統(tǒng)

    基于java springboot + mybatis實(shí)現(xiàn)電影售票管理系統(tǒng)

    這篇文章主要介紹了基于java springboot + mybatis實(shí)現(xiàn)的完整電影售票管理系統(tǒng)基于java springboot + mybatis,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-08-08
  • idea項(xiàng)目中target文件提示拒絕訪問(wèn)的解決

    idea項(xiàng)目中target文件提示拒絕訪問(wèn)的解決

    這篇文章主要介紹了idea項(xiàng)目中target文件提示拒絕訪問(wèn)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • Java執(zhí)行JS腳本工具

    Java執(zhí)行JS腳本工具

    今天小編就為大家分享一篇關(guān)于Java執(zhí)行JS腳本工具,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-12-12
  • Java連接MYSQL數(shù)據(jù)庫(kù)的實(shí)現(xiàn)步驟

    Java連接MYSQL數(shù)據(jù)庫(kù)的實(shí)現(xiàn)步驟

    以下的文章主要描述的是java連接MYSQL數(shù)據(jù)庫(kù)的正確操作步驟,在此篇文章里我們主要是以實(shí)例列舉的方式來(lái)引出其具體介紹
    2013-06-06

最新評(píng)論