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

news2024/11/17 9:49:47

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()即可。返回插入的集合。

    @Override
    public int batchUpdate1(List<ProductEntity> list) {
        return productRepository.saveAll(list).size();
    }

以下为saveAll()的源码,实现在SimpleJpaRepository

    @Transactional
	@Override
	public <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
	@Override
	public <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,wall
      filter:
        wall:
          config: #支持单个事物多条sql语句执行
            multi-statement-allow: true
            none-base-statement-allow: true
          enabled: 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 {

    @Resource
    private EntityManager entityManager;

    /**
     * 批量插入数据
     */
    @Transactional
    @Override
    public 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
    @Override
    public 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语句组成。

    /**
     * 批量新增
     */
    @Transactional
    public 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();
    }

    /**
     * 批量修改
     */
    @Transactional
    public 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.coloradmin.cn/o/1637659.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【PHP】安装指定版本Composer

1、下载指定版本composer.phar文件&#xff1a;https://github.com/composer/composer/releases 2、将下载的文件添加到全局路径&#xff1a; sudo mv composer.phar /usr/local/bin/composer 3、赋予权限&#xff1a; sudo chmod x /usr/local/bin/composer 4、查看compos…

Android 文件传输

经常写adb命令传文件&#xff0c;结果发现Android studio有自带的文件管理器&#xff0c;可以上传下载文件。

模型剪枝-Network Slimming算法分析

代码见文末 论文地址&#xff1a;Learning Efficient Convolutional Networks through Network Slimming ICCV 2017 Open Access Repository 1.概述 由于边缘设备的限制&#xff0c;在模型的部署中经常受到模型大小、运行内存、计算量的限制。之前的方法要么只能解决其中一个…

hdc不是内部或外部命令,也不是可运行的程序或批处理文件。【鸿蒙报错已解决】

文章目录 项目场景:问题描述原因分析:解决方案:此Bug解决方案总结Bug解决方案寄语项目场景: 最近遇到了这个问题,看到网上也有人在询问这个问题,实操了很多网上的解决方案发现并不能解决这个Bug,所以我在解决这个问题后,总结了自己和其他人的解决经验,进行了整理,写…

翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习二

合集 ChatGPT 通过图形化的方式来理解 Transformer 架构 翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习一翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习二翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深…

2024年五一杯高校数学建模竞赛(A题)|钢板切割问题 | 建模解析,小鹿学长带队指引全代码文章与思路

我是鹿鹿学长&#xff0c;就读于上海交通大学&#xff0c;截至目前已经帮200人完成了建模与思路的构建的处理了&#xff5e; 本篇文章是鹿鹿学长经过深度思考&#xff0c;独辟蹊径&#xff0c;通过路径优化解决钢板切割问题。结合贪心算法&#xff0c;Floyd-Warshall等多元算法…

【开发技巧 | 第一篇】Typora快捷键方式给字体添加颜色

文章目录 1.Typora快捷键方式给字体添加颜色1.1下载并安装AutoHotkey1.2新建ahk脚本文件1.3运行ahk脚本文件1.4小结 1.Typora快捷键方式给字体添加颜色 1.1下载并安装AutoHotkey 点击下载: https://autohotkey.com/download/ahk-install.exe下载安装包&#xff0c;执行安装&a…

数据结构算法——链表带环问题——数学深度解析

前言:本节内容主要是讲解链表的两个问题 &#xff1a;1、判断链表是否带环&#xff1b; 2、一个链表有环&#xff0c; 找到环的入口点。 本节内容适合正在学习链表或者链表基础薄弱的友友们哦。 我们先将问题抛出来&#xff0c;友友们可以自己去力扣或者牛客网去找相应题目&…

STM32 F103C8T6学习笔记17:类IIC通信—MLX90614红外非接触温度计

今日学习配置MLX90614红外非接触温度计 与 STM32 F103C8T6 单片机的通信 文章提供测试代码讲解、完整工程下载、测试效果图 本文需要用到的大概基础知识&#xff1a;1.3寸OLED配置通信显示、IIC通信、 定时器配置使用 这里就只贴出我的 OLED驱动方面的网址链接了&#xff1a…

线上线下收银一体化,新零售POS系统引领连锁门店数字化转型-亿发

在市场竞争日益激烈的背景下&#xff0c;没有哪个商家能够永远屹立不倒。随着互联网技术的快速发展&#xff0c;传统的线下门店面临着来自电商和新零售的新型挑战。实体零售和传统电商都需要进行变革&#xff0c;都需要实现线上线下的融合。 传统零售在客户消费之后就与商家失…

群晖Docker部署Java jar包

安装群晖Docker组件Container Manager 打开组件,找到注册表搜索java,安装java映像 安装完成后会在映像目录里展示出来,点击运行 常规设置按需配置即可 这里要设置一个文件夹目录给这个容器来使用 一般来说给个/root来用就够了 设置完成后可以在容器目录里看到容器已经运行起来了…

判断循环链表以及其环入口

文章目录 题目题目链接题目要求 解题思路方法一&#xff1a;哈希表方法二&#xff1a;双指针 进阶思考快指针一次走三步 进阶问题&#xff08;入口点&#xff09;题目链接题目要求问题思路 总结 题目 题目链接 环形链表 题目要求 解题思路 显而易见的是&#xff0c;单纯的遍…

可靠的Mac照片恢复解决方案

当您在搜索引擎搜索中输入“Mac照片恢复”时&#xff0c;您将获得数以万计的结果。有很多Mac照片恢复解决方案声称他们可以在Mac OS下恢复丢失的照片。但是&#xff0c;并非互联网上的所有Mac照片恢复解决方案都可以解决您的照片丢失问题。而且您不应该花太多时间寻找可靠的Mac…

uniapp 对接 Apple 登录

由于苹果要求App使用第三方登录必须要求接入Apple登录 不然审核不过 所以&#xff1a; 一、勾选苹果登录 二、 设置AppId Sign In Apple 设置完成重新生成描述文件 &#xff01;&#xff01;&#xff01;&#xff01;证书没关系 示例代码&#xff1a; async appleLogin…

2024年最新省市县三级行政区划数据(审图号:GS(2024)0650号)

2024年省、市、县三级行政区划数据由国家基础地理信息中心发布&#xff0c;通过《2024版国家地理信息公共服务平台&#xff08;天地图&#xff09;》正式对外提供。这份数据涵盖了最新的省市县三级行政区划信息&#xff0c;更新于2024年1月&#xff0c;提供了详细的矢量数据下载…

食谱管理和餐饮计划应用Mealie

放假除了休闲娱乐&#xff0c;也不能忘了美食 什么是 Mealie &#xff1f; Mealie 是一个自托管的食谱管理和餐饮计划应用&#xff0c;具有 RestAPI 后端和基于 Vue 构建的响应式前端应用&#xff0c;为整个家庭提供愉快的用户体验。通过提供 URL&#xff0c;您可以轻松将食谱…

知识产权 | 守护科技创新之光,共筑知识产权长城

2024年4月26日&#xff0c;迎来了一年一度的世界知识产权日&#xff0c;今年的主题是&#xff1a;“立足创新创造&#xff0c;构建共同未来。” 易我科技是一家专注于数据安全产品研发、生产、销售、服务一体化的高新技术软件企业。易我科技自成立以来&#xff0c;始终秉持尊重…

贝叶斯回归

1. 贝叶斯推断的定义 简单来说&#xff0c;贝叶斯推断 (Bayesian inference) 就是结合“经验 (先验)”和“实践 (样本)”&#xff0c;得出“结论 (后 验)”。 2. 什么是先验&#xff1f; 贝叶斯推断把模型参数看作随机变量。在得到样本之前&#xff0c;根据主观经验和既有知…

微视网媒:引领新媒体时代的视觉先锋

在信息爆炸的时代&#xff0c;内容消费的方式日新月异&#xff0c;而“微视网媒”正是这场媒体变革中的佼佼者。凭借其独特的视角、精湛的制作和广泛的传播渠道&#xff0c;微视网媒不仅改变了人们获取信息的方式&#xff0c;更在不断地塑造着未来的媒体生态。 一、创新内容&am…

STM32中断之TIM定时器详解

系列文章目录 STM32单片机系列专栏 C语言术语和结构总结专栏 文章目录 1. TIM简述 2. 定时器类型 2.1 基本定时器 2.2 通用定时器 2.3 高级定时器 3. 定时中断 4. 代码示例1 5. 代码示例2 1. TIM简述 定时器的基本功能&#xff1a;定时器可以在预定的时间间隔内产生周…