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 下载正版…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...