胖胖的枫叶
主页
博客
产品设计
企业架构
全栈开发
效率工具
数据分析
项目管理
方法论
面试
  • openJdk-docs
  • spring-projects-docs
  • mysql-docs
  • redis-commands
  • redis-projects
  • apache-rocketmq
  • docker-docs
  • mybatis-docs
  • netty-docs
  • journaldev
  • geeksforgeeks
  • 后端进阶
  • 并发编程网
  • 英语肌肉记忆锻炼软件
  • 墨菲安全
  • Redisson-docs
  • jmh-Visual
  • 美团技术
  • MavenSearch
主页
博客
产品设计
企业架构
全栈开发
效率工具
数据分析
项目管理
方法论
面试
  • openJdk-docs
  • spring-projects-docs
  • mysql-docs
  • redis-commands
  • redis-projects
  • apache-rocketmq
  • docker-docs
  • mybatis-docs
  • netty-docs
  • journaldev
  • geeksforgeeks
  • 后端进阶
  • 并发编程网
  • 英语肌肉记忆锻炼软件
  • 墨菲安全
  • Redisson-docs
  • jmh-Visual
  • 美团技术
  • MavenSearch
  • 标签索引
  • 2024年

    • 配置Mac环境
    • 业务知识会计管理
    • 业务知识会计基础
    • 业务知识什么是财务
  • 2023年

    • 项目 Boi
  • 2022年

    • 企业架构故障管理
    • 企业架构开发债务
  • 2021年

    • Python3.8 Matplotlib员工数据分析
    • Python3.8 Matplotlib IP折线图
    • Python3.8 词云 IP地址
    • Redis RediSearch
    • Rust第一个CLI程序
    • Rust所有权
    • Rust函数与控制流
    • Rust变量与数据类型
    • Rust入门
    • 企业架构分布式系统
    • 编程式权限设计
    • Java JVM优化
    • SpringBoot MyBatis 批量
    • SpringBoot 测试Mock
    • SpringBoot Redis布隆过滤器
    • CentOS7 Jenkins 部署
    • SpringBoot WebClient
    • Docker Drone 部署
    • SpringBoot MyBatis
    • SpringBoot Redisson
    • SpringBoot MyBatis 雪花算法
    • Java Netty
    • Redis 扫描
    • CentOS7 Jenkins本地部署分级
    • Mac 安装 Neo4j Jupyter
    • Mac OpenJDK11 JavaFX 环境
    • Mac 安装 Jenv
    • SpringBoot Redis 延时队列
    • SpringBoot MDC日志
    • SpringBoot 定时任务
    • CentOS7 Nginx GoAccess
    • SpringBoot MyBatis 分析
    • SpringBoot Lucene
    • 企业架构分布式锁
    • 学习技巧减少学习排斥心理
    • SpringBoot 动态数据源
    • Docker Compose SpringBoot MySQL Redis
    • SpringBoot 阻塞队列
    • Docker Compose Redis 哨兵
    • Docker Compose Redis 主从
    • 网络通信
  • 2020年

    • SpringBoot 延时队列
    • MySQL基础(四)
    • Java 雪花算法
    • Redis Geo
    • 网络通信 Tcpdump
    • Spring SPI
    • Java Zookeeper
    • SpringBoot JMH
    • 网络通信 Wireshark
    • Docker Compose Redis MySQL
    • CentOS7 Docker 部署
    • Netty 源码环境搭建
    • MySQL基础(三)
    • CentOS7 Selenium运行环境
    • CentOS7 Nginx HTTPS
    • Java JMH
    • SpringBoot 修改Tomcat版本
    • Java Eureka 钉钉通知
    • SpringBoot 错误钉钉通知
    • Java JVM
    • Git 合并提交
    • CentOS7 OpenResty 部署
  • 2019年

    • Redis CLI
    • CentOS7 Nginx 日志
    • 编程式代码风格
    • IDEA 插件
    • Skywalking 源码环境搭建
    • SpringBoot Redis 超时错误
    • 编程式 gRPC
    • Java Arthas
    • Docker Compose Redis 缓存击穿
    • Docker ElasticSearch5.6.8 部署
    • Docker Mysql5.7 部署
    • Spring Redis 字符串
    • Docker Zookeeper 部署
    • Docker Redis 部署
    • SpringBoot Dubbo
    • CentOS7 CMake 部署
    • 应用程序性能指标
    • Java Code 递归
    • CentOS7 ELK 部署
    • CentOS7 Sonarqube 部署
    • Java Selenium
    • Java JJWT JUnit4
    • Spring 源码环境搭建
    • Java JUnit4
    • Java Web JSON Token
    • 编程式 FastDFS
    • Java XPath
    • Redis基础(二)
    • Redis基础(一)
    • Java MyBatis JUnit4
    • Java MyBatis H2 JUnit4
    • MyBatis 源码环境搭建
    • Git 配置
    • Java 核心
    • Java Dubbo
    • Java JavaCollecionsFramework
    • Java Maven
    • Java MyBatis
    • Java Spring
    • Java SpringMVC
    • MySQL
    • Redis
  • 2018年

    • Java HashMap
    • Java HashSet
    • Java Code 交换值
    • Spring Upgrade SpringBoot
    • Mac 编程环境
    • Java Log4j
    • 网络通信 Modbus
    • MySQL基础(二)
    • MySQL基础(一)
    • Java Stack
    • Java Vector
    • CentOS7 RabbitMQ 部署
    • CentOS7 Redis 部署
    • CentOS7 MongoDB 部署
    • CentOS7 基础命令
    • Java Eureka Zookeeper
    • CentOS7 MySQL 部署
    • Git 分支
    • CentOS7 Java环境配置
    • Java LinkedList
    • Java ArrayList
    • Spring Annotation Aop

SpringBoot MyBatis 批量

开发过程中经常会出现批量写入数据库的操作,特别是后台系统,在导入数据的场景下会对表性能造成一定影响。

Mybatis-ONE-SQL

SQL插入主要使用INSERT语句,有两种常见的用法。

逐条插入

  • 如果插入的记录过多,比如大于20条记录,性能损耗非常严重。
INSERT INTO `table_data` ('field1','field2') VALUES ('data1','data2');

批量插入

  • 在日常开发中使用最多的方式,但是数量大比如大于100条的时候插入性能非常差。
INSERT INTO `table_data` ('field1','field2') VALUES ('data1','data2'),('data1','data2');

Mybatis-Foreach-SQL

插入数据有两种实现,foreach、batchExecutor。

    <insert id="batchInsert">
        INSERT INTO `table_data`(`create_time`)
        VALUES
        <foreach collection="tableDataList" item="item" index="index" separator=",">
            (#{item.createTime})
        </foreach>
    </insert>

Mybatis-Batch-SQL

  • 配合foreach执行
@Component
@Slf4j
public class BatchDao {

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    public <T,M> boolean batchSave(Collection<T> entityList, Class<M> mapper, BiConsumer<M,Collection<T>> fuc) {
        SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
        M entityMapper = sqlSession.getMapper(mapper);
        try {
            fuc.accept(entityMapper, entityList);
            sqlSession.flushStatements();
            sqlSession.clearCache();
            return true;
        } catch (Exception e) {
            log.error("{}",e.getMessage());
            sqlSession.rollback();
        } finally {
            sqlSession.close();
        }
        return false;
    }
}

测试结果

  • 10条数据 foreach > batch > insert
  • 100条数据 foreach > batch > insert
  • 1000条数据 foreach > batch > insert
  • 5000条数据 batch > foreach (数据量大,batch优势就出来了)

测试代码

/**
 * @author z201.coding@gmail.com
 **/
@Slf4j
@State(Scope.Benchmark)
public class AppApplicationJMHTest {

    private final static Integer MEASUREMENT_ITERATIONS = 1;
    private final static Integer WARMUP_ITERATIONS = 1;

    static ConfigurableApplicationContext context;

    protected TableDataDao tableDataDao;

    protected BatchDao batchDao;

    protected SqlSessionFactory sqlSessionFactory;

    @Setup(Level.Trial)
    public synchronized void initialize() {
        try {
            String args = "";
            if (context == null) {
                context = SpringApplication.run(AppApplication.class, args);
            }
            tableDataDao = context.getBean(TableDataDao.class);
            batchDao = context.getBean(BatchDao.class);
            sqlSessionFactory = context.getBean(SqlSessionFactory.class);
            tableDataDao.truncate();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // Throughput 整体吞吐量,例如”1秒内可以执行多少次调用”。
    // AverageTime: 调用的平均时间,例如”每次调用平均耗时xxx毫秒”。
    // SampleTime: 随机取样,最后输出取样结果的分布,例如”99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”
    // SingleShotTime: 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。
    // All(“all”, “All benchmark modes”);
    @Test
    public void executeJmhRunner() throws RunnerException {
        Options opt = new OptionsBuilder()
                // 设置类名正则表达式基准为搜索当前类
                .include("\\." + AppApplicationJMHTest.class.getSimpleName() + "\\.")
                // 都是一些基本的参数,可以根据具体情况调整。一般比较重的东西可以进行大量的测试,放到服务器上运行。
                .warmupIterations(WARMUP_ITERATIONS) // 多少次预热
                .measurementIterations(MEASUREMENT_ITERATIONS) // 要做多少次测量m
                .timeUnit(TimeUnit.MILLISECONDS) // 毫秒
                // 不使用多线程
                .forks(0) // 进行 fork 的次数。如果 fork 数是2的话,则 JMH 会 fork 出两个进程来进行测试。
                .threads(1) // 每个进程中的测试线程,这个非常好理解,根据具体情况选择,一般为cpu乘以2。
                .mode(Mode.AverageTime)
                .shouldDoGC(true)
                .shouldFailOnError(true)
                .resultFormat(ResultFormatType.JSON) // 输出格式化
                .result("/dev/null") // set this to a valid filename if you want reports
//                .result("benchmark.json")
                .shouldFailOnError(true)
                .jvmArgs("-server")
                .build();
        new Runner(opt).run();
    }

    private List<TableData> mockData(int size) {
        List<TableData> list = new ArrayList<>();
        Long time = Clock.systemDefaultZone().millis();
        for (int i = 0; i < size; i++) {
            list.add(TableData.builder().createTime(time + i).build());
        }
        return list;
    }

    @Benchmark
    public void insert() {
        List<TableData> tableDataList = mockData(10);
        tableDataList.stream().forEach(i -> tableDataDao.insert(i));
    }

    @Benchmark
    public void batchInsert() {
        List<TableData> tableDataList = mockData(10);
        tableDataDao.batchInsert(tableDataList);
    }

    @Benchmark
    public void batchSave() {
        List<TableData> tableDataList = mockData(10);
        boolean result = batchDao.batchSave(tableDataList, TableDataDao.class, (mapper, data) -> {
            mapper.batchInsert(tableDataList);
        });
    }


    @Benchmark
    public void insert100() {
        List<TableData> tableDataList = mockData(100);
        tableDataList.stream().forEach(i -> tableDataDao.insert(i));
    }

    @Benchmark
    public void batchInsert100() {
        List<TableData> tableDataList = mockData(100);
        tableDataDao.batchInsert(tableDataList);
    }

    @Benchmark
    public void batchSave100() {
        List<TableData> tableDataList = mockData(100);
        boolean result = batchDao.batchSave(tableDataList, TableDataDao.class, (mapper, data) -> {
            mapper.batchInsert(tableDataList);
        });
    }

    @Benchmark
    public void insert1000() {
        List<TableData> tableDataList = mockData(1000);
        tableDataList.stream().forEach(i -> tableDataDao.insert(i));
    }

    @Benchmark
    public void batchInsert1000() {
        List<TableData> tableDataList = mockData(1000);
        tableDataDao.batchInsert(tableDataList);
    }

    @Benchmark
    public void batchSave1000() {
        List<TableData> tableDataList = mockData(1000);
        boolean result = batchDao.batchSave(tableDataList, TableDataDao.class, (mapper, data) -> {
            mapper.batchInsert(tableDataList);
        });
    }

    @Benchmark
    public void batchInsert5000() {
        List<TableData> tableDataList = mockData(5000);
        tableDataDao.batchInsert(tableDataList);
    }

    @Benchmark
    public void batchSave5000() {
        List<TableData> tableDataList = mockData(5000);
        boolean result = batchDao.batchSave(tableDataList, TableDataDao.class, (mapper, data) -> {
            mapper.batchInsert(tableDataList);
        });
    }

}

最近更新: 2025/12/27 18:51
Contributors: 庆峰
Prev
Java JVM优化
Next
SpringBoot 测试Mock