SpringBoot +MyBatis批量插入数据

💍 背景介绍

在最近的开发过程中,遇到了往数据库中表中插入大量的数据。有一个全国银行各分行的信息,共计148032条数据

文件有8.45MB,因为考虑到数据量比较大,就想着导入到MySQL看一看需要多长时间。

💍 方案一:用 for语句循环插入(不推荐)

使用for循环语句将,将数据一条条插入。

sql 复制代码
insert into t_bank values (?, ?, ?, ?, ?)
java 复制代码
 /**
     * 导入银行信息
     *
     * @param bankList
     * @return java.lang.String
     */
    @Override
    public String importBank(List<TBank> bankList) {
        if (StringUtils.isNull(bankList) || bankList.size() == 0)
        {
            throw new CustomException("导入用户数据不能为空!");
        }
        long start = System.currentTimeMillis();
        for (int i = 0; i < bankList.size(); i++) {
            tBankMapper.insertTBank(bankList.get(i));
        }
        long end = System.currentTimeMillis();
        log.info("数据总耗时:" + (end-start) + "ms" );
        return "Success";
    }

优势:JDBC 中的 PreparedStatement 有预编译功能,预编译之后会缓存起来。

之后SQL执行会比较快,且 JDBC可以开启批处理,这个批处理执行非常给力。

劣势:这种方式插入大量数据时,效率非常底下,不推荐。很多时候我们的 SQL 服务器和应用服务器可能并不是同一台,所以必须要考虑网络 IO。

如果网络 IO 比较费时间的话,那么可能会拖慢 SQL 执行的速度。

💍 方案二:利用mybatis的foreach来实现循环插入(不推荐)

sql 复制代码
insert into t_bank values (?, ?, ?, ?, ?) , (?, ?, ?, ?, ?) , (?, ?, ?, ?, ?)
java 复制代码
/**
     * 导入银行信息
     *
     * @param bankList
     * @return java.lang.String
     */
    @Override
    public String importBank(List<TBank> bankList) {
        if (StringUtils.isNull(bankList) || bankList.size() == 0)
        {
            throw new CustomException("导入用户数据不能为空!");
        }
        long start = System.currentTimeMillis();
        tBankMapper.batchInsert(bankList);
        long end = System.currentTimeMillis();
        log.info("数据总耗时:" + (end-start) + "ms" );
        return "Success";
    }
xml 复制代码
<insert id="batchInsert" parameterType="java.util.List">
        insert into t_bank (
                            bank_id,
                            branch_name,
                            bank_code,
                            contact_line,
                            parent_id,
                            branch_province,
                            branch_province_name,
                            branch_city,
                            branch_city_name)
        values
        <foreach collection="list" item="item" separator=",">
            (
                #{item.bankId},
                #{item.branchName},
                #{item.bankCode},
                #{item.contactLine},
                #{item.parentId},
                #{item.branchProvince},
                #{item.branchProvinceName},
                #{item.branchCity},
                #{item.branchCityName})
        </foreach>
    </insert>

优势:不用频繁访问数据库,一条sql搞定,效率比较高。

劣势:一当数据量太大时,会出现拼接的sql语句超长而执行失败,所以当数据量太大时,也不推荐。

二是 SQL 太长了,甚至可能需要分片后批量处理。

三是无法充分发挥 PreparedStatement 预编译的优势,SQL 要重新解析且无法复用

💍 第三种方案,使用sqlSessionFactory实现批量插入(推荐)

java 复制代码
	@Resource
    private SqlSessionFactory sqlSessionFactory;
java 复制代码
 /**
     * 导入银行信息
     *
     * @param bankList
     * @param isUpdateSupport
     * @return java.lang.String
     * @author PuWenshuo
     * @date 2023/9/18 11:32
     */
    @Override
    public String importBank(List<TBank> bankList, Boolean isUpdateSupport) {
        if (StringUtils.isNull(bankList) || bankList.size() == 0)
        {
            throw new CustomException("导入用户数据不能为空!");
        }
        String msg="";
        long start = System.currentTimeMillis();
        // 指定分页大小
        int pageSize = 1000; // 每批插入1000条数据
        // 关闭session的自动提交
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
        try {
            TBankMapper bankMapper = sqlSession.getMapper(TBankMapper.class);
            // 计算总页数
            int totalSize = bankList.size();
            int totalPages = (int) Math.ceil((double) totalSize / pageSize);
            for (int page = 1; page <= totalPages; page++) {
                // 计算当前页的起始和结束索引
                int startIndex = (page - 1) * pageSize;
                int endIndex = Math.min(startIndex + pageSize, totalSize);

                // 获取当前页的数据
                List<TBank> banks = bankList.subList(startIndex, endIndex);
                // 批量插入数据
                tBankMapper.batchInsert(banks);
                // 提交事务
                sqlSession.commit();
            }
            msg="恭喜您,数据已全部导入成功!";
        } catch (Exception e) {
            sqlSession.rollback();
            log.error(e.getMessage());
            msg="很抱歉,导入失败!";
        } finally {
            sqlSession.close();
        }
        long end = System.currentTimeMillis();
        log.info("数据总耗时:" + (end-start) + "ms" );
        return msg;
    }

优势:这种方式可以说是集第一种和第二种方式的优点于一身,既可以提高运行效率,又可以保证大数据量时执行成功,大数据量时推荐使用这种方式。

相关推荐
间彧20 分钟前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧24 分钟前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端
间彧29 分钟前
如何结合CI/CD流水线自动选择正确的Docker Compose配置?
后端
间彧30 分钟前
在多环境(开发、测试、生产)下,如何管理不同的Docker Compose配置?
后端
间彧32 分钟前
如何为Docker Compose中的服务配置健康检查,确保服务真正可用?
后端
间彧36 分钟前
Docker Compose和Kubernetes在编排服务时有哪些核心区别?
后端
间彧41 分钟前
如何在实际项目中集成Arthas Tunnel Server实现Kubernetes集群的远程诊断?
后端
brzhang1 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构
lang201509281 小时前
Spring Boot日志配置完全指南
java·spring boot·单元测试
草明2 小时前
Go 的 IO 多路复用
开发语言·后端·golang