Spring Data JPA数据批量插入、批量更新真的用对了吗

Spring Data JPA系列

1、SpringBoot集成JPA及基本使用

2、Spring Data JPA Criteria查询、部分字段查询

3、Spring Data JPA数据批量插入、批量更新真的用对了吗

前言

在前两篇文章已经介绍过,在使用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的知识点还有很多,限于篇幅,本篇先分享到这里。

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/316559.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【基础算法总结】双指针算法二

双指针 1.有效三角形的个数2.和为S的两个数字3. 三数之和4.四数之和 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.有效三角形的个数 题目…

react实现时钟翻牌效果

需求&#xff1a;随着数字的变动要求有时钟翻动动效 问题&#xff1a;只在加载时有动效 解决方案&#xff1a;通过判断数字改变&#xff08;这里通过新旧数值变动来判断&#xff0c;不贴代码啦&#xff09;&#xff0c;每次变动的时候手动把animationIterationCount设置为inf…

Android --- 网络请求

通常在 Android 中进行网络连接一般使用 Scoket 和HTTP&#xff0c;HTTP 请求方式比 Scoket 多。HTTP 请求一般采用原生的 HttpClient 和 HttpUrlConnection 的两种网络访问方式&#xff08;系统自带的&#xff09;。但是在 Android 5.0 的时候 Google 就不推荐使用 HttpClient…

Python自学篇3-PyCharm开发工具下载、安装及应用

一、Python开发工具 自学篇1中讲到了安装Python之后出现的几个应用程序&#xff0c;其中IDLE、Python.exe都可以用来编写python程序&#xff0c;也可以进行调试&#xff1b;但是比较基础&#xff0c;比较原始&#xff0c;调试不方便&#xff0c;界面也不友好&#xff0c;需要更…

笔记本电脑耗电和发热比较厉害怎么处理

工作中会遇到有同事反馈笔记本电脑耗电和发热比较厉害&#xff0c;主要检查以下几个地方 1、CPU频率 很多人觉得是cpu使用率高就代表电脑跑得快&#xff0c;发热量就大&#xff0c;其实不是的&#xff0c;主要是看的cpu频率&#xff0c;频率越高&#xff0c;电脑发热量越大。如…

Visual Studio中怎样更改Nuget程序包源

场景 Visual Studio 2019 在使用NuGet添加依赖包时&#xff0c;在预览中搜索不到程序包。 排查下NuGet的程序包源为本地。 将程序包源修改下。 实现 在解决方案上右击选择管理解决方案中的NuGet程序包(在 Visual Studio 中打开“工具”>“选项”>“NuGet 包管理器”…

idea上传项目到gitee(码云)

1、打开码云&#xff0c;新建仓库 2、创建 3、这就是创建成功的页面 4、复制仓库地址&#xff0c;后面需要用到 2、打开我们的项目&#xff1a;例如我现在的项目 1、idea创建git仓库 2、选择我们项目文件夹的目录 3、查看文件是否变色&#xff0c;变色表示成功了 4、添加到缓…

STM32的GPIO输入和输出函数详解

系列文章目录 STM32单片机系列专栏 C语言术语和结构总结专栏 文章目录 1. GPIO模式 2. GPIO输出 2.1 RCC 2.2 GPIO 3. 代码示例 3.1 RCC时钟 3.2 GPIO初始化 3.3 GPIO输出函数 3.4 推挽输出和开漏输出 4. GPIO输入 4.1 输入模式 4.2 数据读取函数 5. C语言语法 1…

为什么很多企业都使用OV SSL证书

我们要了解什么是SSL OV证书 SSL OV证书&#xff0c;即组织验证型SSL证书&#xff0c;它要求证书颁发机构对申请证书的组织进行身份验证&#xff0c;确认组织的真实性后&#xff0c;才会发放证书。这种验证方式提高了安全性&#xff0c;因为它确保了证书背后的实体是真实存在的…

C语言(操作符)1

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;关注收藏&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#x…

基于Springboot的点餐平台

基于SpringbootVue的点餐平台的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页展示 菜品信息 菜品资讯 购物车 后台登录 用户管理 菜品分类管理 菜品信息管理 …

【Linux】dlopen: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29‘ not found

[30116] Error loading Python lib /tmp/_MEIlvdUu6/libpython3.8.so.1.0: dlopen: /lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.29 not found (required by /tmp/_MEIlvdUu6/libpython3.8.so.1.0)1 cd到指定路径 cd /usr/local 2 下载 wget http://ftp.gnu.org/gnu/gl…

NXP i.MX8系列平台开发讲解 - 3.10 Linux PCIe资源分配与访问(二)

专栏文章目录传送门&#xff1a;返回专栏目录 目录 1. PCIe BFD 2. PCIe 配置空间 2.1 PCIe 配置空间访问 PCIe I/O访问方法 PCIe MMIO访问方法 3. PCIe BAR相关 4. PCIe Capbility 5. PCIe 操作 本文将重点讲解PCIe的资源访问相关内容&#xff0c;对于PCIe资源访问是从…

设计不外流,保护创意的同时锁住图纸安全!

在设计行业中&#xff0c;图纸和创意文稿的安全至关重要&#xff0c;因为它们体现了企业的创新能力和核心竞争力。华企盾DSC数据防泄密系统提供了一系列功能&#xff0c;可以有效地保护这些珍贵的设计和文档不被外泄。以下是如何利用华企盾DSC系统保障设计图纸安全的关键措施&a…

【工具】-根源上解决VScode打印输出乱码的问题

目录 1 第一步&#xff1a; 改编译命令&#xff0c;保持一致2 第二步&#xff1a; 更改VScode的编码格式-保持一致 1 第一步&#xff1a; 改编译命令&#xff0c;保持一致 看一下你的控制台的编译的命名后缀&#xff0c;有两个关键的参数&#xff0c;如下图&#xff1a; “-f…

LT9611UXC双端口 MIPI DSI/CSI 转 HDMI2.0,带音频

1. 说明 LT9611UXC 是一款高性能 MIPI DSI/CSI 至 HDMI2.0 转换器。MIPI DSI/CSI 输入具有可配置的单端口或双端口&#xff0c;具有 1 个高速时钟通道和 1~4 个高速数据通道&#xff0c;工作速率最高为 2Gbps/通道&#xff0c;可支持高达 16Gbps 的总带宽。 LT9611UXC 支持突发…

双目深度估计原理立体视觉

双目深度估计原理&立体视觉 0. 写在前面1. 双目估计的大致步骤2. 理想双目系统的深度估计公式推导3. 双目标定公式推导4. 极线校正理论推导 0. 写在前面 双目深度估计是通过两个相机的对同一个点的视差来得到给该点的深度。 标准系统的双目深度估计的公式推导需要满足:1)两…

【算法每日一练】

蛮有意思的的一道题&#xff0c;最后要判断能否成为一种1~n的全排列&#xff0c;我最这样做的&#xff1a; 整个数组先排序一下。假设遍历到了i&#xff0c;那就判断前面b和r的个数&#xff0c;但是有想到了后面可能还会对前面的结果产生影响&#xff0c;所以就抛弃了这个想法…

二、再识VUE-MVVM

一、初识VUE 二、再识VUE-MVVM 三、VUE数据代理 MVVM Vue.js 专注于 MVVM 模型的 ViewModel 层。它通过双向数据绑定把 View 层和 Model 层连接了起来。实际的 DOM 封装和输出格式都被抽象为了 Directives 和 Filters。 ViewModel 一个同步 Model 和 View 的对象。在 Vue.js…

Stable Diffusion基础:ControlNet之线稿成图

今天继续给大家分享Stable Diffusiion的基础能力&#xff1a;ControlNet之线稿成图。 所谓线稿就是由一条条的线段组成的图形&#xff0c;主要用于绘画和设计领域的打底稿、表达构想和预见最终效果。 所谓线稿成图就是利用 Stable Diffusion ControlNet 的能力&#xff0c;依…