不在傻傻for循环!完美解决JPA批量插入问题

本文介绍了一种使用SpringJdbcTemplate和MySQL进行批量数据插入的优化方法,通过拼接SQL语句并一次性执行,避免了JPA saveAll方法的性能瓶颈,显著提升了大批量数据插入的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:jpa在简单的增删改查方面确实帮助我们节省了大部分时间,但是面对复杂的情况就显得心有余而力不足了,最近遇到一个批量插入的情况,jpa虽然提供了saveAll方法,但是底层还是for循环save,如果遇到大量数据插入频繁与数据库交互必然会对性能造成影响,查阅了很多资料也没有一个满意的结果,最后还是自己想出了一个方案
mysql是支持insert多个values的,所以我的思路就是拼sql然后利用SpringJdbcTemplate的excute方法执行原始sql,直接一次性插入,附上源码

StringBuilder insert = new StringBuilder("INSERT INTO `user_message` (`created_at`, `is_deleted`, `content`,`send_tm`,`status`,`type`,`user_id`) VALUES ");
        for (int i = 0; i < users.size(); i++) {
            insert.append("(").append("NOW()").append(",0,").append("'").append(content).append("'").append(",").append("'").append(format).append("'").append(",0,").append(type).append(",").append(users.get(i)).append(")");
            if (i < users.size() - 1) {
                insert.append(",");
            }
        }
        log.info("SQL语句:{}", JSON.toJSON(insert));
        try {
            jdbcTemplate.execute(insert.toString());
            return true;
        } catch (Exception e) {
            throw new BizException("sql解析错误");
        }

拼出来的sql语句

INSERT INTO `user_message` (`created_at`, `is_deleted`, `content`,`send_tm`,`status`,`type`,`user_id`) VALUES (NOW(),0,'fdsfadsfdaf','2019-12-18',0,8,1),(NOW(),0,'fdsfadsfdaf','2019-12-18',0,8,2),(NOW(),0,'fdsfadsfdaf','2019-12-18',0,8,3),(NOW(),0,'fdsfadsfdaf','2019-12-18',0,8,4),(NOW(),0,'fdsfadsfdaf','2019-12-18',0,8,5)

在这里插入图片描述
成功插入

这里只是把关键部分贴出来,了解思路就好

### 使用 JPA 批量插入的最佳实践 JPA 提供了一种抽象的方式来处理数据库操作,而批量插入是一种常见的需求。为了优化性能并减少资源消耗,在执行批量插入时可以遵循一些最佳实践。 #### 配置批处理大小 通过设置 `hibernate.jdbc.batch_size` 属性来启用 JDBC 的批处理功能。该属性定义每次提交到数据库的操作数量。通常建议将其值设为 10 到 50 之间[^4]。 ```properties spring.jpa.properties.hibernate.jdbc.batch_size=30 ``` #### 禁用二级缓存和查询缓存 在大批量数据插入过程中,禁用 Hibernate 的二级缓存和查询缓存能够显著提升性能。这是因为这些缓存在高吞吐场景下可能会成为瓶颈[^5]。 ```java spring.jpa.properties.hibernate.cache.use_second_level_cache=false spring.jpa.properties.hibernate.cache.use_query_cache=false ``` #### 清理持久化上下文 当向数据库写入大量记录时,持久化上下文中会积累许多对象实例。这可能导致内存占用过高甚至引发 OutOfMemoryError。因此,每完成一批次的数据插入后应调用 `entityManager.clear()` 方法释放内存[^6]。 ```java for (int i = 0; i < entities.size(); i++) { entityManager.persist(entities.get(i)); if (i % batchSize == 0 && i != 0) { // 当达到批次大小时刷新并清理 entityManager.flush(); entityManager.clear(); } } ``` #### 设置 ID 生成策略 对于支持自增列的数据库(如 MySQL 或 PostgreSQL),推荐使用 `IDENTITY` 作为主键生成器;而对于其他类型的数据库,则可以选择 `SEQUENCE` 或者 `TABLE` 方式。需要注意的是,如果采用序列机制,请确保其增量与批处理大小一致以提高效率[^7]。 ```java @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; ``` 或者: ```java @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence_generator") @SequenceGenerator(name="sequence_generator", sequenceName = "my_sequence", allocationSize=batchSize) private Long id; ``` #### 减少事务开销 尽可能在一个事务内完成所有的批量插入工作,这样不仅可以降低锁竞争还能加快速度。但是要注意控制单个事务持续时间不要太长以免影响并发访问[^8]。 ```java @Transactional public void bulkInsert(List<Entity> entities){ int batchSize = Integer.parseInt(env.getProperty("batch.size")); for(int i=0;i<entities.size();i++){ repository.save(entities.get(i)); if((i+1)%batchSize==0 || i==(entities.size()-1)){ entityManager.flush(); entityManager.clear(); } } } ``` --- 相关问题
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值