Spring Data JPA数据批量插入、批量更新真的用对了吗
Spring Data JPA系列
1、SpringBoot集成JPA及基本使用
2、Spring Data JPA Criteria查询、部分字段查询
3、Spring Data JPA数据批量插入、批量更新真的用对了吗
4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作
前言
在前两篇文章已经介绍过,在使用Spring Data JPA时,DAO层的Respository通过继承JPARepository,自动提供了基本的CRUD接口。以下为JPARepository提供的接口。

通过接口的名字能够很直观的了解到JPA提供的批量插入的接口为saveAll()。
saveAll()批量更新
saveAll()方法的使用很简单,接收一个集合对象。在Service层中调用repository.saveAll()即可。返回插入的集合。
@Overridepublic int batchUpdate1(List<ProductEntity> list) {return productRepository.saveAll(list).size();}
以下为saveAll()的源码,实现在SimpleJpaRepository
@Transactional@Overridepublic <S extends T> List<S> saveAll(Iterable<S> entities) {Assert.notNull(entities, "Entities must not be null!");List<S> result = new ArrayList<>();// 遍历集合for (S entity : entities) {// 调用save()方法保存对象result.add(save(entity));}return result;}@Transactional@Overridepublic <S extends T> S save(S entity) {Assert.notNull(entity, "Entity must not be null.");// 如果数据的新的,则执行persist()插入数据if (entityInformation.isNew(entity)) {em.persist(entity);return entity;} else {// 否则的话,执行merge(),该方法相当于hibernate中session的saveOrUpdate()方法,// 用于实体的插入和更新操作;return em.merge(entity);}}
从源码可以看出,saveAll()方法不仅支持数据的批量保存,还执行批量更新。使用起来会非常方便。
但是看了这个源码,是否有些疑惑,为何是轮询集合,然后调用save()方法,这会不会有问题?理想中的批量操作,应该是一批次访问一次数据库,减少数据库的访问,提升数据更新的效率。调用上面的方法,执行10001条数据的更新后,打开druid,截图如下:

什么情况?执行了10001次事务,这说明此批量操作并非理想中的批量操作,它只是帮忙封装了一个for遍历而已。而为了解决真正的批量操作,可以使用强大的javax.persistence.EntityManager来实现,在save()方法中的persist()、merge()方法也是EntityManager中的方法。在上一篇
Spring Data JPA Criteria查询、部分字段查询-CSDN博客
部分字段查询也是用的EntityManager。
EntityManager
Java Persistence API(JPA)中的EntityManager是一个接口,在JPA规范中,EntityManager扮演着执行持久化操作的关键角色。普通Java对象只有被EntityManager持久化之后,才能转变为持久化对象,保存到数据库中。它不仅可以管理和更新Entity对象,还可以基于主键查询Entity对象,通过JPQL语句进行Entity查询,甚至通过原生SQL语句进行数据库更新及查询操作。
EntityManager提供以下功能:
1)创建、更新和删除数据:EntityManager中的persist()、merge()和remove()方法分别用于插入、更新和删除数据库记录;
2)查询数据:EntityManager的find()和createQuery()方法用于查询数据;
3)管理实体的生命周期:EntityManager的flush()方法用于将持久性上下文同步到基础数据库,进行持久化操作;
4)事务管理:EntityManager的getTransaction()方法用于获取当前事务,可以对事务进行提交或回滚;
5)执行原生SQL:EntityManager的createNativeQuery()方法用于执行原生SQL。对于原生SQL,需要考虑不同数据库的各自实现;
6)创建CriteriaBuilder:EntityManager的getCriteriaBuilder()方法用于获取CriteriaBuilder。通过CriteriaBuilder实现使用Criteria API查询数据;
EntityManager提供了一种抽象的方式来管理数据库操作,使得开发者可以更多专注于业务逻辑的开发,而不需要关心底层的SQL语句。
以下将要介绍的数据库数据的批量新增以及修改使用的就是EntityManager执行原生SQL实现的。
MySQL数据批量新增及修改
使用EntityManager执行MySQL原生SQL实现时,需要先修改spring.database.url的配置:
spring:datasource:url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false&allowMultiQueries=true
在url中,要加上
allowMultiQueries=true
另外,如果一次传输的数据太多,还需要在MySQL数据库中进行配置,windows修改配置文件my.ini,Linux修改配置文件my.cnf,在配置中添加group_concat_max_len的设置。该值默认值为1024个字节。
[mysqld]
group_concat_max_len = 102400
如果有使用第三方的数据库中间件,也可能需要进行配置,否则可能被当作SQL注入攻击。如使用druid,需要如下配置:
spring:datasource:druid:filters: stat,wallfilter:wall:config: #支持单个事物多条sql语句执行multi-statement-allow: truenone-base-statement-allow: trueenabled: true
批量新增及修改代码如下:
package com.jingai.jpa.service.impl;import com.jingai.jpa.dao.entity.ProductEntity;
import com.jingai.jpa.service.ProductService;
import org.apache.logging.log4j.util.Strings;
import org.hibernate.query.criteria.internal.OrderImpl;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.StringUtils;import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.List;@Service
public class ProductServiceImpl implements ProductService {@Resourceprivate EntityManager entityManager;/*** 批量插入数据*/@Transactional@Overridepublic int batchInsert(List<ProductEntity> list) {int batchSize = 2000;if(list.isEmpty()) {return 0;}// 单次批量插入条数StringBuffer sb = new StringBuffer();String insertSql = "insert into tb_product(name, delivery_no, customer, security_code, create_time, validate_num) values ";sb.append(insertSql);SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");int index = 0;for(ProductEntity entity : list) {index ++;// 拼接sql字符串sb.append("('").append(entity.getName()).append("','").append(entity.getDeliveryNo()).append("','").append(entity.getCustomer()).append("','").append(entity.getSecurityCode()).append("','").append(format.format(entity.getCreateTime())).append("', 0),");if(index % batchSize == 0) {Query query = entityManager.createNativeQuery(sb.substring(0, sb.length() - 1) + ";");int rs = query.executeUpdate();if(rs != batchSize) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return 0;}sb = sb.replace(0, sb.length(), insertSql);index = 0;}}if(index > 0) {Query query = entityManager.createNativeQuery(sb.substring(0, sb.length() - 1) + ";");int rs = query.executeUpdate();if(rs != list.size() % batchSize) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return 0;}}return list.size();}/*** 批量修改*/ @Transactional@Overridepublic int batchUpdate3(List<ProductEntity> list) {if(list.isEmpty()) {return 0;}StringBuffer sb = new StringBuffer();String updateSql = "update tb_product set ";for(int i = 0 ; i < list.size() ; i ++) {ProductEntity entity = list.get(i);sb.append(updateSql).append("name = '").append(entity.getName()).append("', customer = '").append(entity.getCustomer()).append("' where pid = ").append(entity.getPid()).append(";");if(i > 0 && i % 2000 == 0) {Query query = entityManager.createNativeQuery(sb.toString());int rs = query.executeUpdate();if(rs <= 0) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return 0;}sb = sb.replace(0, sb.length(), Strings.EMPTY);}}if(sb.length() > 0) {Query query = entityManager.createNativeQuery(sb.toString());int rs = query.executeUpdate();if(rs <= 0) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return 0;}}return list.size();}
}
执行上面的batchUpdate3的接口,修改10000条记录,druid中的记录如下:

相比上面使用的saveAll()方法实现的批量修改,时间少了1900ms,而实际的访问体验,相差好几秒。因为这个执行时间只记录了在数据库中执行的时间,而saveAll()要访问10000次的数据库,而batchUpdate3()只需要访问5次数据库。
Oracle数据批量新增及修改
Oracle数据库并不支持多语句操作。在Oracle数据库中,需要使用存储过程的begin...end语句块,该语句块由一组一起执行的SQL语句组成。
/*** 批量新增*/@Transactionalpublic int batchInsert(List<Varinst> list) {if(list.isEmpty()) {return 0;}// 单次批量插入条数int batchSize = 2000;StringBuffer sb = new StringBuffer();String insertSql = "insert into tb_product(name, delivery_no, customer, security_code, create_time, validate_num) values ";sb.append("begin\r\n");SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");int index = 0;for(Varinst entity : list) {index ++;sb.append("('").append(entity.getName()).append("','").append(entity.getDeliveryNo()).append("','").append(entity.getCustomer()).append("','").append(entity.getSecurityCode()).append("',TO_DATE('").append(format.format(entity.getCreateTime())).append("', 'SYYYY-MM-DD HH24:MI:SS'), 0),");if(index % batchSize == 0) {sb.append("end;");Query query = entityManager.createNativeQuery(sb.toString());int rs = query.executeUpdate();if(rs <= 0) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return 0;}sb = sb.replace(0, sb.length(), Strings.EMPTY);index = 0;sb.append("begin\r\n");}}if(index > 0) {sb.append("end;");Query query = entityManager.createNativeQuery(sb.toString());int rs = query.executeUpdate();if(rs <= 0) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return 0;}}return list.size();}/*** 批量修改*/@Transactionalpublic int batchUpdateOracle(List<ProductEntity> list) {StringBuffer sb = new StringBuffer();sb.append("begin\r\n");String updateSql = "update ACT_HI_VARINST set ";for(int i = 0 ; i < list.size() ; i ++) {ProductEntity entity = list.get(i);sb.append(updateSql).append("name = '").append(entity.getName()).append("', customer = '").append(entity.getCustomer()).append("' where pid = ").append(entity.getPid()).append(";");if(i > 0 && i % 2000 == 0) {sb.append("end;");Query query = entityManager.createNativeQuery(sb.toString());int rs = query.executeUpdate();if(rs <= 0) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return 0;}sb = sb.replace(0, sb.length(), Strings.EMPTY);sb.append("begin\r\n");}}if(list.size() % 2000 != 0) {sb.append("end;");Query query = entityManager.createNativeQuery(sb.toString());int rs = query.executeUpdate();if(rs <= 0) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return 0;}}return list.size();}
结尾
Spring Data JPA的知识点还有很多,限于篇幅,本篇先分享到这里。
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。
相关文章:
Spring Data JPA数据批量插入、批量更新真的用对了吗
Spring Data JPA系列 1、SpringBoot集成JPA及基本使用 2、Spring Data JPA Criteria查询、部分字段查询 3、Spring Data JPA数据批量插入、批量更新真的用对了吗 4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作 前言 在前两篇文章已经…...
数据结构-线性表-应用题-2.2-12
1)算法的基本设计思想:依次扫描数组的每一个元素,将第一个遇到的整数num保存到c中,count记为1,若遇到的下一个整数还是等于num,count,否则count--,当计数减到0时,将遇到的下一个整数保存到c中,计…...
目录页码右对齐快速解决
选择目录–段落–制表符,按图中设置即可...
分红76.39亿,分红率再创新高,成长活力无限的伊利带来丰厚回报
伊利47万股东,又等来了一个好消息。 4月29日,伊利股份发布2023年报,实现营业总收入1261.79亿元,归母净利润104.29亿元,双创历史新高,实现连续31年稳健增长。 在递交亮眼成绩单的同时,乳业巨头伊…...
关于行进线路。
https://map.tianditu.gov.cn/ 作者:Chockhugh 链接:https://www.zhihu.com/question/20545559/answer/494685117 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 以50km,几乎全是…...
Unity 编辑器工具 - 资源引用查找器
目录 1.功能概述 2.完整代码 3. 实现原理 4. 使用预览 5.新增优化版本 在Unity项目开发过程中,管理和维护资源之间的引用关系是至关重要的。当然我们项目也是需要这个功能 毕竟项目大了之后查找资源引用还是交给 资源引用查找器 比较好。 1.功能概述 资源引用…...
MySQL中的批量更新实战
MySQL中的批量更新实战 表结构 mysql> desc dept; --------------------------------------------------------- | Field | Type | Null | Key | Default | Extra | --------------------------------------------------------- | deptno | int(11) …...
为软件教学文档增加实践能力
为了更方便软件教学,我们在凌鲨(OpenLinkSaas)上增加了公共资源引用的功能。 目前可以被引用的公共资源: 微应用常用软件公共知识库Docker模板 引用公共资源 引用微应用 目前微应用包含了主流数据库,终端等工具,可以方便的进行各种相关实…...
39-2 Web应用防火墙 - WAF数据库层绕过
如果你本地没有安装mysql就先安装一下:4-2 MySQL 的下载与安装_mysql5.7.9.1下载-CSDN博客 一、数据库层绕过简介 绕过数据库层通常用于规避Web应用防火墙(WAF)的SQL注入防护规则。攻击者需要利用数据库特性,寻找规避常规安全策略的方法。这里涉及到不同数据库的特性、SQ…...
薪酬激励策略:留住企业核心人才的关键
在竞争激烈的商业环境中,企业为了保持竞争力和市场地位,必须高度重视人才的管理和发展。企业的核心人才是推动企业发展的关键因素,因此,如何有效地激励和留住这些核心人才,成为企业持续发展的关键之一。薪酬激励策略作…...
【bbs02补】注册功能form组件-前端-后端-总结、登录功能(前端、后端、生成验证码)
1 注册功能 1.1 注册功能form组件 1.2 注册功能前端 1.3 注册功能后端 1.4 forms组件和前后端总结 2 登录功能 2.1 登录前端 2.2 生成验证码 1 注册功能 1.1 注册功能form组件 # 注册页面-用户名-密码-确认密码-邮箱-手机号-头像# form组件 可以帮助我们1 快速生成前端页面2 数…...
MindSponge分子动力学模拟——定义一个分子系统
技术背景 在前面两篇文章中,我们分别介绍了分子动力学模拟软件MindSponge的软件架构和安装与使用。这里我们进入到实用化阶段,假定大家都已经在本地部署好了基于MindSpore的MindSponge的编程环境,开始用MindSponge去做一些真正的分子模拟的工…...
unity想让方法带一个默认参数怎么写
在C#中,包括Unity使用的C#版本,你可以为方法参数提供默认值。这允许你在调用方法时省略某些参数,并使用这些参数的默认值。以下是如何为一个方法参数设置默认值的示例: using UnityEngine; public class MyClass : MonoBehaviou…...
从零开始的软件测试学习之旅(六)测试网络基础知识
测试网络基础知识 HTTP和HTMLURLDNS客户端和服务器请求方法和状态码面试高频Fiddler抓包工具教学弱网 HTTP和HTML 概念 html: HyperText Markup Language 超文本标记语言 http: HyperText Transfer Protocol 超文本传输协议 超文本: 图片, 音频, 视频 关系:http 可以对 html 的…...
NSS题目练习
[SWPUCTF 2021 新生赛]gift_F12 通过题目提示可以知道flag应该可以在源代码中找到 查看源代码,直接用 ctrlf 搜索flag即可 [SWPUCTF 2021 新生赛]jicao 题目打开后能看到一串php代码,要求是用post传参传入idwllmNB以及用get传参传入json[x]"wllm&q…...
Springboot+vue项目零食销售商城
摘要 随着科学技术的飞速发展,社会的方方面面、各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势,零食销售商城当然也不能排除在外。零食销售商城是以实际运用为开发背景,运用软件工程原理和开发方法ÿ…...
cesium 雷达遮罩(电弧球效果)
cesium 雷达遮罩(电弧球效果) 以下为源码直接复制可用 1、实现思路 通过修改“material”材质来实现轨迹球效果 2、代码示例 2.1 index.html <!DOCTYPE html> <html lang="en"><head><!...
W801学习笔记二十三:语文和英语学习应用的代码汇总
前面几章,代码经过重构,可能有点乱。这里给个最终版本,以供参考。 1、应用基类: IScean.h enum SceanResult{SceanResult_EXIT 1, SceanResult_Done 2 };class IScean {public:IScean();virtual ~IScean();// 纯虚函数virtu…...
安卓LayoutParams浅析
目录 前言一、使用 LayoutParams 设置宽高二、不设置 LayoutParams2.1 TextView 的 LayoutParams2.2 LinearLayout 的 LayoutParams 三、getLayoutParams 的使用四、setLayoutParams 的作用五、使用 setWidth/setHeight 设置宽高 前言 先来看一个简单的布局,先用 x…...
UltralSO制作启动盘时报错:磁盘/映像容量太小解决办法
UltralSO制作启动盘时报错:磁盘/映像容量太小解决办法 发现网上随便下载的UltralSO制作启动盘时报错:磁盘/映像容量太小,导致制作启动盘出错 解决方案: 去这个地址下载:https://cn.ultraiso.net/xiazai.html 下载正版…...
华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
