mybatis以及mybatisplus批量插入问题

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

1. 思路分析

批量插入是我们日常开放经常会使用到的场景一般情况下我们也会有两种方案进行实施如下所示。

方案一 就是用 for 循环循环插入

优点JDBC 中的 PreparedStatement 有预编译功能预编译之后会缓存起来后面的 SQL 执行会比较快并且JDBC 可以开启批处理这个批处理执行非常给力。

缺点很多时候我们的 SQL 服务器和应用服务器可能并不是同一台所以必须要考虑网络 IO如果网络 IO 比较费时间的话那么可能会拖慢
SQL 执行的速度。

再来说第二种方案就是生成一条 SQL 插入

优势这种方案的优势在于只有一次网络 IO即使分片处理也只是数次网络 IO所以这种方案不会在网络 IO 上花费太多时间。

缺点一是 SQL 太长了甚至可能需要分片后批量处理

缺点二是无法充分发挥 PreparedStatement 预编译的优势SQL 要重新解析且无法复用三是最终生成的 SQL
太长了数据库管理器解析这么长的 SQL 也需要时间。


2. rewriteBatchedStatements=true

在jdbc连接后面加上 rewriteBatchedStatements=true 加上后才是真正的批量插入。

 jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&rewriteBatchedStatements=true

3.使用mybatis批量插入

方案一使用foreach进行插入生成一条 SQL 插入
mapper文件

   <insert id="save" parameterType="java.util.List">
        INSERT INTO test
        (
        id,
        a,
        b,
        c
        )
        VALUES
        <foreach collection="list" item="item" index="index" separator=",">
            (
            #{item.id},
            #{item.a},
            #{item.b},
            #{item.c}
            )
        </foreach>
    </insert>

调用方法

 @Override
    public void add() {
        //时间 一
        long l = System.currentTimeMillis();
        List<TestEntity> list=new ArrayList<>();
        for (int i=0;i<1000;i++){
            TestEntity testEntity=new TestEntity();
            testEntity.setC(i);
            list.add(testEntity);
        }
       testMapper.save(list);
        //时间 二
        long l1 = System.currentTimeMillis();
        System.out.println("耗时"+(l1-l));
    }

插入了1000条数据耗时535毫秒。
插入了50000条数据直接报错。
报错原因是因为我们一条SQL进行插入导致SQL太长
解决办法
1.修改MySQL配置
2.对新增数据进行分片


方案二一条条插入

mapper

   <insert id="addUserOneByOne" parameterType="com.ruoyi.system.domain.TestEntity">
    insert into test (id,a,b,c) values (#{id},#{a},#{b},#{c})
    </insert>

测试代码

@Service
public class TestServiceimpl extends ServiceImpl<TestMapper, TestEntity> implements TestService {

    @Autowired
  private   TestMapper testMapper;

    @Autowired
  private SqlSessionFactory sqlSessionFactory;
    
    public void addUserOneByOne(List<TestEntity> users) {
        SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
        TestMapper um = session.getMapper(TestMapper.class);
        long startTime = System.currentTimeMillis();
        for (TestEntity user : users) {
            um.addUserOneByOne(user);
        }
        session.commit();
        long endTime = System.currentTimeMillis();
        System.out.println("耗时"+(endTime - startTime));
    }
}

插入了1000条数据耗时959毫秒。
插入50000条数据耗时11214毫秒。


对比分析
如果我们批量插入少部分数据可以使用方式一一条SQL进行插入。这样是比较快的。
如果我们插入数据达到1w条10来万条这时建议用方式二进行插入是比较快的。


4. 使用mybatisplus批量插入

使用saveBatch方法进行批量插入

@Service
public class TestServiceimpl extends ServiceImpl<TestMapper, TestEntity> implements TestService {
    
    @Autowired
  private   TestMapper testMapper;

    @Autowired
  private SqlSessionFactory sqlSessionFactory;

    @Override
    public void add() {
        //时间 一
        long l = System.currentTimeMillis();
        List<TestEntity> list=new ArrayList<>();
        for (int i=0;i<50000;i++){
            TestEntity testEntity=new TestEntity();
            testEntity.setC(i);
            list.add(testEntity);
        }
        saveBatch(list);
        //时间 二
        long l1 = System.currentTimeMillis();
        System.out.println("耗时"+(l1-l));
    }

插入50000条数据耗时19516毫秒

源码分析

   public boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE);
        return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            sqlSession.insert(sqlStatement, entity);
        });
    }

这里注意 return 中的第三个参数是一个 lambda 表达式这也是 MP 中批量插入的核心逻辑可以看到MP 先对数据进行分片默认分片大小是 1000分片完成之后也是一条一条的插入。

 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", new Object[0]);
        return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, (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();
                }
            }

        });
    }

继续查看 executeBatch 方法就会发现这里的 sqlSession 其实也是一个批处理的 sqlSession并非普通的 sqlSession。和我们mybatis使用的方法二一致。


5业务场景一对多怎么处理

比如如下这种一对多场景。
新增的时候保存都好理解形成一个数组一起保存。
而修改的时候就有点难处理了比如我修改了第二条删除了第三条这时统一保存应该怎么处理

在这里插入图片描述

使用 ON DUPLICATE KEY UPDATE 发生主键冲突就更新没有发生主键冲突就新增

有时候由于业务需求可能需要先去根据某一字段值查询数据库中是否有记录有则更新没有则插入。这个时候就可以用到ON DUPLICATE key update这个sql语句了

mapper如下所示

   <insert id="save" parameterType="java.util.List">
        INSERT INTO test
        (
        id,
        a,
        b,
        c
        )
        VALUES
        <foreach collection="list" item="item" index="index" separator=",">
            (
            #{item.id},
            #{item.a},
            #{item.b},
            #{item.c}
            )
        </foreach>
        ON DUPLICATE KEY UPDATE
        id=id,
        a = VALUES(a) ,
        b = VALUES(b),
        c = VALUES(c)
    </insert>

或者在使用mybatisplus时使用saveOrUpdate方法进行一条数据的新增或更新。 saveOrUpdateBatch()方法进行批量数据的新增或更新。


梅西“消除厄运”卡
在这里插入图片描述

新冠退退退
梅老板冲冲冲
三星阿根廷加油!

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6