浅谈mybatis-plus批量保存异常及效率优化

 更新时间:2024年01月16日 08:30:40   作者:斗码士  
本文主要介绍了mybatis-plus批量保存异常及效率优化,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

最近基于自己公司内部服务维护,发现其中调度中心近期出现不少错误日志,但是该任务却是正常执行,生成的报表数据也是正常的,所以很多天没有发现问题

这就匪夷所思了,经仔细排查发现,是触发了feign超时hystrix熔断器机制

也就是说子服务出现了执行时间过长的情况

是什么让它花费这么多时间去执行呢,只有一个for循环,组装list<object>

这个组装过程在java看来是非常快,根本不可能出现问题

我发现了

 iXxxxService.saveBatch(xxxx);

mybatisplus3.3.2自带的批量保存的sql接口

跟踪代码的实现

在接口发现IService

   @Transactional(
        rollbackFor = {Exception.class}
    )
    default boolean saveBatch(Collection<T> entityList) {
        return this.saveBatch(entityList, 1000);
    }

    boolean saveBatch(Collection<T> entityList, int batchSize);

ServiceImpl的实现 

 @Transactional(
        rollbackFor = {Exception.class}
    )
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = this.sqlStatement(SqlMethod.INSERT_ONE);
        return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            sqlSession.insert(sqlStatement, entity);
        });
    }
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        Assert.isFalse(batchSize < 1, "batchSize must not be less than one", new Object[0]);
        return !CollectionUtils.isEmpty(list) && this.executeBatch((sqlSession) -> {
            int size = list.size();
            int i = 1;

            for(Iterator var6 = list.iterator(); var6.hasNext(); ++i) {
                E element = var6.next();
                consumer.accept(sqlSession, element);
                if (i % batchSize == 0 || i == size) {
                    sqlSession.flushStatements();
                }
            }

        });
    }

可以看到这个是累计到一定数量一起 flush。

很多人认为这是mybatisplus设计的一个缺陷,是一条一条去做插入,其实这是错误,这种写法不仅没错还写的非常负责,具体接下来看

方法一

首先要结合数据库驱动来配合,大家注意这个 rewriteBatchedStatements 玩意,其实mybatisplus批量保存与这个的首肯有很大关系

没有加它之前

这是没加之前最好的成绩

加了之后最差的成绩

可以非常直观的看出效率明显提高了好几倍,所以呢千万别误会mybatisplus这个设计,人家完全交给你自主控制,你非得说是它的问题这就不好了

方法二

相比上面方法一的就比较粗暴了

我直接拿过来重写saveBatch,或者增加一个特殊的批量保存

第一步

继承mybatisplus自带的BaseMapper(这里为什么要继承我就不说了哈,懂的都懂),添加我们自定义的批量保存方法

zxsSaveBatch

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import java.util.Collection;
public interface BaseMapperPlus <T> extends BaseMapper<T> {

    Integer zxsSaveBatch(Collection<T> entityList);

}

第二步

继承AbstractMethod,因为我们要改写它的批量插入语句,换成我们自己想要实现的方式

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.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
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 java.util.List;
import java.util.function.Predicate;

@NoArgsConstructor
@AllArgsConstructor
public class ZxsSaveBatch 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;
        // 表包含主键处理逻辑,如果不包含主键当普通字段处理
        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 "zxsSaveBatch";
    }
}

第三步

继承DefaultSqlInjector,把我们的方法添加进去

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;

import java.util.List;

public class ZxsSqlIntorPlus extends DefaultSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);//获取之前所有的方法
        methodList.add(new ZxsSaveBatch()); //添加自己的方法
        return methodList;
    }

}

第四步

注入到spring

@Bean
    public ZxsSqlIntorPlus zxsSqlIntorPlus() {
        return new ZxsSqlIntorPlus();
    }

第五步

使用方法

之前我们是通过service.saveBatch(Xxxx)来实现批量插入的

这里我们需要改个地方,将原本的BaseMapper改成我们新创建的BaseMapperPlus

这是我的例子,当然,你也可以是其它的

public interface TestMapper extends BaseMapper<Test> {
}

改为

public interface TestMapper extends BaseMapperPlus<Test> {
}

然后在service里面通过baseMapper.zxsSaveBatch(Xxxx)

当然你也可以重写mybatisplus中IService的批量保存的

测一下速度,发现不用设置rewriteBatchedStatements,执行速度也更快了,几乎和rewriteBatchedStatements=true的速度相当

到此这篇关于浅谈mybatis-plus批量保存异常及效率优化的文章就介绍到这了,更多相关mybatis-plus批量保存异常内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 深入了解Java虚拟机栈以及内存模型

    深入了解Java虚拟机栈以及内存模型

    这篇文章主要介绍了深入了解Java虚拟机栈以及内存模型,文中有非常详细的代码示例,对正在学习java的小伙伴们有很大的帮助,需要的朋友可以参考下
    2021-04-04
  • Java实现计网循环冗余检验算法的方法示例

    Java实现计网循环冗余检验算法的方法示例

    这篇文章主要给大家介绍了关于Java实现计网循环冗余检验算法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • Java实现动态获取图片验证码的示例代码

    Java实现动态获取图片验证码的示例代码

    这篇文章主要介绍了Java实现动态获取图片验证码的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • 关于Android触摸事件分发的原理详析

    关于Android触摸事件分发的原理详析

    触摸事件分发机制一直以来都是Android中比较重要的一大块,自定义view,各种复杂的自定义手势交互都与触摸事件分发机制关系密,下面这篇文章主要给大家介绍了关于Android触摸事件分发原理的相关资料,需要的朋友可以参考下
    2022-01-01
  • SpringMVC拦截器零基础掌握

    SpringMVC拦截器零基础掌握

    拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行。本文将详细讲讲SpringMVC中拦截器的概念及入门案例,感兴趣的可以尝试一下
    2023-03-03
  • IDEA 2021.2 激活教程及启动报错问题解决方法

    IDEA 2021.2 激活教程及启动报错问题解决方法

    这篇文章主要介绍了IDEA 2021.2 启动报错及激活教程,文章开头给大家介绍了idea2021最新激活方法,关于idea2021启动报错的问题小编也给大家介绍的非常详细,需要的朋友可以参考下
    2021-10-10
  • 聊聊注解@Aspect的AOP实现操作

    聊聊注解@Aspect的AOP实现操作

    这篇文章主要介绍了聊聊注解@Aspect的AOP实现操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • 关于springboot中nacos动态路由的配置

    关于springboot中nacos动态路由的配置

    这篇文章主要介绍了springboot中nacos动态路由的配置方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Java详细分析梳理垃圾回收机制

    Java详细分析梳理垃圾回收机制

    垃圾回收,顾名思义,便是将已经分配出去的,但却不再使用的内存回收回来,以便能够再次分配。在 Java 虚拟机的语境下,垃圾指的是死亡的对象所占据的堆空间
    2022-04-04
  • java使用观察者模式异步短信/邮箱提醒用户群

    java使用观察者模式异步短信/邮箱提醒用户群

    这篇文章主要为大家详细介绍了java使用观察者模式异步短信和邮箱提醒用户群,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-11-11

最新评论