当前位置: 首页 > news >正文

JPA/Hibernate 批量插入实战:告别低效,实现真正的 MySQL 批量写入

JPA/Hibernate 批量插入实战:告别低效,实现真正的 MySQL 批量写入


前言

列位看官都知道,在使用 JPA/Hibernate 处理海量数据插入或更新时,如果简单地循环调用 save()saveAll(),后端执行的往往是一条条独立的 SQL 语句。每一次操作都伴随着网络往返和数据库交互,这在数据量庞大时,性能会急剧下降,造成巨大的I/O开销。

别担心,JPA/Hibernate 早就提供了批量操作(Batch Operations) 的支持,可以将多个相同的写操作合并成一个批次发送给数据库执行,从而显著提升性能。

但是,开启批量插入并非易事,它是一个涉及 JPA 实体配置、Hibernate 属性设置JDBC 驱动参数三者协同工作的系统工程。如果任何一步配置错误,都可能导致批量模式失效,退化为低效的单条插入。

本文将为您揭示在 Spring Boot 3 环境下,如何为 JPA 实体配置并开启针对 MySQL 数据库的真正批量写入功能。


批量插入的三大关键条件(先说结论)

要成功开启并启用真正的批量插入,必须同时满足以下三个核心条件:

1. ID 生成策略:避开性能陷阱

主键(ID)的生成策略是批量操作的首要障碍。

  • 陷阱:GenerationType.IDENTITY
    如果实体使用 GenerationType.IDENTITY(例如 MySQL 的自增ID),Hibernate 必须在每次 INSERT 执行后立即从数据库获取新生成的 ID。这会强制 Hibernate 逐条发送 INSERT 语句,彻底破坏批量模式。

  • 解决方案:使用预分配策略
    必须选择允许 Hibernate 在执行插入前预先获取一批 ID 的策略,例如 GenerationType.SEQUENCEGenerationType.TABLE

2. JDBC 驱动:开启“重写”功能

这是实现真正多值 INSERT 语句的核心。

  • 关键参数:rewriteBatchedStatements=true
    MySQL 的 JDBC 驱动需要这个特定的参数。它指示驱动将接收到的多个批量操作“重写”为一条高效的、多值的 INSERT 语句(例如 INSERT INTO ... VALUES (...), (...), (...))。
3. Hibernate 配置:明确批量大小和排序

必须显式告知 Hibernate 开启批量模式,并设置性能优化参数。

  • 核心配置:hibernate.jdbc.batch_size:设置每次批处理的语句数量。
  • 优化:hibernate.order_inserts: true:允许 Hibernate 对相同类型的 INSERT 语句进行排序,最大化批处理效果。

实战步骤:配置实体与环境

我们将以一个名为 User 的实体为例进行配置。

第 1 步:配置实体的 ID 生成策略(User.java)

为了避免 IDENTITY 策略对批处理的破坏,我们采用 TABLE 策略来预分配主键。

import jakarta.persistence.*;@Entity
@Table(name = "t_user")
public class User {@Id@GeneratedValue(strategy = GenerationType.TABLE, generator = "id_share_gen")@TableGenerator(name = "id_share_gen",table = "id_share_gen", // 存储序列号的表名pkColumnName = "sequence_name", // 区分不同序列的列名valueColumnName = "next_val", // 存储下一个序列值的列名pkColumnValue = "batch_user", // 本实体使用的序列名allocationSize = 1000, // 每次从数据库预取的ID数量initialValue = 60000 // 初始值)private Long id;// ... 其他实体字段
}

说明: allocationSize = 1000 建议配置得大于或等于您在下一步配置的 batch_size。Hibernate 会一次性预取 1000 个 ID 到内存中,只有当这些 ID 用完后才会再次访问数据库,极大地减少了数据库交互次数。

第 2 步:创建并初始化 ID 生成表(SQL)

根据 @TableGenerator 的配置,我们需要提前在数据库中创建并插入初始值:

-- 创建用于存储序列的表
CREATE TABLE id_share_gen (`sequence_name` VARCHAR(128) NOT NULL PRIMARY KEY,`next_val` BIGINT NOT NULL
) ENGINE=InnoDB;-- 为 'batch_user' 序列插入初始值
INSERT INTO id_share_gen (`sequence_name`, `next_val`) VALUES ('batch_user', 60000);
第 3 步:配置 application.yml

在 Spring Boot 的配置文件中,我们必须同时配置 JDBC URL 和 Hibernate 的批量参数。

spring:datasource:# 核心:URL中必须包含 rewriteBatchedStatements=trueurl: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: your_usernamepassword: your_passworddriver-class-name: com.mysql.cj.jdbc.Driverjpa:hibernate:ddl-auto: none # 生产环境建议 none 或 validateshow-sql: false # 调试时可以开启properties:hibernate:# 1. 设置 JDBC 的批处理大小(例如 100)jdbc:batch_size: 100 # 2. 开启批量插入排序,优化性能order_inserts: true# 3. 开启批量更新排序,优化性能order_updates: true

核心解析: rewriteBatchedStatements=true 是 MySQL JDBC 驱动的“魔术”,它将多个单条 INSERT 语句转换为一条高效的多值 INSERT 语句,这是实现真正批量插入的关键。

第 4 步:在代码中调用批量保存

配置完成后,在业务代码中,只需在事务(@Transactional)环境下调用 Spring Data JPA 提供的 saveAll() 方法即可触发批量操作。

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;@Service
public class UserService {private final UserRepository userRepository;// 假设构造函数注入@Transactionalpublic void batchSaveUsers(List<User> users) {// 当 users 列表的 size 很大时,JPA/Hibernate 会自动根据配置进行分批处理userRepository.saveAll(users);}
}

如何验证批量操作是否生效?

修改配置后,最重要的一步是验证它是否真的生效了。通过程序层面的 JPA/Hibernate 日志很难准确判断是否是批量操作(不管开启JPA的还是Hibernate的sql log,亦或jdbc的都不能进行客观地观察),最好的方式是观察数据库接收到的实际 SQL 语句

1. 开启 MySQL General Log

登录到 MySQL 服务器,执行以下命令开启并设置日志输出到表(便于查询):

SET GLOBAL log_output = 'TABLE';
SET GLOBAL general_log = 'ON';
2. 运行代码并查询日志

执行您的批量插入程序(调用 userService.batchSaveUsers(...)),然后查询 mysql.general_log 表:

SELECT event_time, argument
FROM mysql.general_log
WHERE command_type = 'Query'
AND argument LIKE 'INSERT INTO t_user%' -- 替换为您的表名
ORDER BY event_time DESC
LIMIT 50;
3. 结果分析
  • 如果批量操作已生效:
    您将在 argument 列中看到类似下面这样的单条合并后的 SQL 语句(多值插入):

    INSERT INTO t_user (name, email, id) VALUES 
    ('user1', 'email1', 60001),
    ('user2', 'email2', 60002), 
    ..., 
    ('user100', 'email100', 60100)
    

    这条 SQL 一次性插入了多条记录。

  • 如果批量操作未生效:
    您会看到多条独立的 INSERT 语句,每条语句只插入一条记录:

    INSERT INTO t_user (name, email, id) VALUES ('user1', 'email1', 60001)
    INSERT INTO t_user (name, email, id) VALUES ('user2', 'email2', 60002)
    ...
    
4. 关闭 General Log(重要!)

general_log 会记录所有查询,对数据库性能有较大影响,验证完毕后请务必关闭它:

SET GLOBAL general_log = 'OFF';

结语

JPA 批量插入是提升数据处理效率的利器,但它要求配置的严谨性。核心在于:使用预分配ID策略,并在 JDBC URL 中启用 rewriteBatchedStatements=true,最后通过 hibernate.jdbc.batch_size 激活 Hibernate 的批处理机制。

掌握了正确的方法和验证手段,就能告别低效的单条插入,让您的应用程序在处理大数据时也能保持飞速!希望这篇小文能在列位看官优化性能时有所帮助!

http://www.dtcms.com/a/442311.html

相关文章:

  • 做企业网站需要准备什么材料口碑最好的旅游软件排名
  • 鸿蒙开发4--鸿蒙页面导航Router与参数传递详解
  • 容器生命周期与管理策略
  • 依托 Amazon Bedrock 生成式 AI 能力,结合 Slack 生态与亚马逊云科技服务构建企业级图像生成 App 的全流程解析
  • 设计师可以做兼职的网站国外优惠卷网站如何做
  • 小企业常用的进销存软件有哪些
  • Filebeat+Kafka+ELK 日志采集实战
  • 『C++成长记』一颗会搜索的二叉树
  • 【经验分享】JWE 详解:比 JWT 更安全的令牌技术
  • 【连载6】数据库未来发展趋势展望,附例子,避坑指南以及面试题
  • 【深度学习计算机视觉】09:语义分割和数据集——核心概念与关键技术解析
  • 直播网站建设重庆数据分析师35岁以后怎么办
  • 【Ray大模型分布式训练】
  • 浦东做营销网站天津网站建设制作
  • 网站建设网银江西门户网站建设
  • [初学C语言]C语言数据类型和变量
  • 资源提示符
  • 人机协同如何突破功能分配的 “天花板”?
  • Spring Cloud Netflix Ribbon:微服务的客户端负载均衡利器
  • Docker 数据卷与存储机制(持久化与共享实战)
  • 做环评工作的常用网站电商网站分析
  • 【常用字符串相关函数】
  • unsigned 是等于 unsigned int
  • 营销型企业网站建设案例网站建设功能分为几种
  • 2058. 找出临界点之间的最小和最大距离
  • Leetcode 347. 前 K 个高频元素 堆 / 优先队列
  • python 做网站 案例那个网站专利分析做的好
  • 获得场景视频API开发(01):CC视频平台分片上传服务的设计与实践
  • Spring Boot整合缓存——Ehcache缓存!超详细!
  • 数据结构 之 【LRU Cache】(注意list的splice接口函数)