在JPA和EJB中用乐观锁解决并发问题

同一条记录不同的用户都有权限修改,如:有一条记录编号为100,有一个字段price,张三修改price的值为200,李时修改其值为300。后修改的会覆盖前修改的,张三在修记录编号为100的记录过程中,中途去了躺厕所,回来继续修改。而李四在张三上厕所的过程中已经将数据修改为300,张三上厕所回来后修改为200,张三的修改覆盖了李四的修改,李四不知情。

这些便是并发产生的问题。

正常情况是:李三和李四同时修改同一条记录,后保存的不应该覆盖先保存的; 被删除的记录不能再被修改,而且要给用户提示。

数据库中的并发是指 DBMS 在不损害数据完整性或一致性的情况下管理来自不同用户或进程的许多并发活动或事务的能力。考虑到许多应用程序需要多个用户或进程对数据库进行并发访问,并发是现代数据库的重要组成部分。

尝试同时读取或写入数据库可能会导致出现多个问题,包括:

  • 数据一致性 — 同时处理数据库中的数据的事务可能会导致数据的准确性和有效性出现问题。
  • 死锁 — 由于 2 个或多个事务尝试更改和访问数据库中的相同资源,因此可能会发生死锁。这就像不同的进程相互阻止彼此获得所需的资源,从而导致无限等待。
  • 性能 — 过多的锁定和事务序列化会在数据库响应时间中发挥重要作用,这是由对数据库中数据的并发访问触发的。


好消息是,数据库产品提供了开箱即用的解决方案,您可以使用锁定、隔离级别、时间戳或多版本并发控制 (MVCC) 来克服这些问题。

在本文中,我们将使用 JPA 版本注释,通过利用 Optimistic Lock 来帮助克服其中的一些问题。

乐观锁定与悲观锁定

根据您正在处理的数据的性质,您可以选择正确的方法来锁定 Database records。

  • 乐观锁定 — 在这种方法中,多个用户可以尝试使用乐观锁定同时更改同一记录,而不会意识到其他用户的尝试,并且只有当其他用户尝试提交其并发更新时,他们才会收到存在冲突的警报。
  • 悲观锁定 — 悲观锁定方法禁止并发记录更新。一旦一个人开始更新记录,就会对记录应用锁定,并且尝试更新此记录的用户会收到另一个用户当前正在进行更新的警报。通俗地说悲观锁锁定数据记录后别的用户无法再修改,直到当前用户修改完成。

使用 @Version 的乐观锁定

使用 @Version 非常简单。您只需在 Entity 类中有一个具有注释的字段,它是以下类型之一:int、Integer、long、Long、short、Short。

@Entity
public class Product {@Idprivate Long id;private String name;private BigDecimal price;@Versionprivate Integer version;}

数据库中必须有对应的字段,否则不行。

JPA 提供了两种不同的乐观锁模式:

  • OPTIMISTIC – 对于具有 version 属性的所有实体,您将获得乐观读取锁。
  • OPTIMISTIC_FORCE_INCREMENT — 就像 OPTIMISTIC 一样,但 version 属性值增加了一个增量。

以下是一些如何使用它的示例。

entityManager.find(Product.class, id, LockModeType.OPTIMISTIC);

或者使用注释来加锁。

@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
@Query("SELECT p FROM Product p WHERE p.Id = ?1")
public Optional<Product> getProductById(Long id);

使用@version和OPTIMISTIC 后,做并发操作会报OptimisticLockException异常:

cannot be merged because it has changed or been deleted since it was last read.

这个异常OptimisticLockException在Backing Bean中捕获不到,必须自定义异常类。

自定义并发异常类

已删除的异常
import jakarta.ejb.EJBException;public class DeletedException extends EJBException {private static final long serialVersionUID = -3077279713283364443L;public DeletedException() {super();}public DeletedException(String message) {super(message);}public DeletedException(Exception ex) {super(ex);}public DeletedException(String message, Exception ex) {super(message, ex);}}

将这个类继承于EJBException,可以被Backing Bean捕获。

已修改的异常

import jakarta.ejb.EJBException;public class ChangedException extends EJBException {private static final long serialVersionUID = -1013228368211446590L;public ChangedException() {super();}public ChangedException(String message) {super(message);}public ChangedException(Exception ex) {super(ex);}public ChangedException(String message, Exception ex) {super(message, ex);}}

检查版本抛出异常

修改前进行加锁

/*** LockModeType.PESSIMISTIC_FORCE_INCREMENT 这是排他锁。当使用排他锁时,不能再被编辑,但是可以直接删除。** @param id* @return*/public T edit(Long id) {return getEntityManager().find(entityClass, id, LockModeType.OPTIMISTIC);}

修改前和更新前进行版本检查。版本检查依赖于实体是否有属性version。

@Override
public Warehouse edit(Long id) {Warehouse persistedEntity = em.find(Warehouse.class, id);if (persistedEntity == null) {throw new DeletedException("数据已被删除");}return super.edit(id); 
}@Override
public Warehouse update(Warehouse entity) throws OptimisticLockException {Warehouse persistedEntity = em.find(Warehouse.class, entity.getId());if (persistedEntity == null) {throw new DeletedException("数据已被删除");}if (persistedEntity != null && !persistedEntity.getVersion().equals(entity.getVersion())) {throw new ChangedException("数据已被修改");}return super.update(entity); 
}

若实体不存在抛出异常,若实体版本不同也抛出异常。

在Backing Bean中捕获异常

Backing Bean是与视图层交互的逻辑层,应该所有的异常的在这里被捕获并转换为好友的操作提示。

public void edit(Long id) {try {this.current = warehouseBean.edit(id);PrimeFaces.current().executeScript("PF('manageWarehouseDialog').show()");} catch (DeletedException ex) {facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "提示", "数据已被另一用户删除,不可修改"));PrimeFaces.current().ajax().update("form:messages", "form:dt-warehouses");}
}public void save() {if (this.current.getId() == null) {try {warehouseBean.save(this.current);this.data = warehouseBean.getJpaLazyDataModel();LOGGER.log(Level.CONFIG, "已新增仓库:{0}", new Object[]{this.current.getName()});facesContext.addMessage(null, new FacesMessage("提示", "已新增仓库 " + this.current.getName()));} catch (EJBException ex) {facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "告警", "已存在同名的仓库"));}} else {try {warehouseBean.update(this.current);LOGGER.log(Level.CONFIG, "已更新仓库:{0}", new Object[]{this.current.getName()});facesContext.addMessage(null, new FacesMessage("提示", "已更新仓库 " + this.current.getName()));} catch (ChangedException ex) {facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "提示", "仓库" + this.current.getName() + "已被另一用户修改,修改失败"));} catch (DeletedException ex) {facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "提示", "仓库" + this.current.getName() + "已被另一用户删除,修改失败"));}}PrimeFaces.current().executeScript("PF('manageWarehouseDialog').hide()");PrimeFaces.current().ajax().update("form:messages", "form:dt-warehouses");
}

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

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

相关文章

在 ASP.NET Core 6.0 中使用 Swagger/OpenAPI 丰富 Web API 文档

示例代码&#xff1a;https://download.csdn.net/download/hefeng_aspnet/89961435 介绍 在选择或尝试与 API 集成之前&#xff0c;大多数开发人员都会查看其 API 文档。保持 API 文档更新以反映软件更改是一项挑战&#xff0c;需要时间和精力。对于 Web API&#xff0c;我们…

tensorflow案例5--基于改进VGG16模型的马铃薯识别,准确率提升0.6%,计算量降低78.07%

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 本次采用VGG16模型进行预测&#xff0c;准确率达到了98.875&#xff0c;但是修改VGG16网络结构&#xff0c; 准确率达到了0.9969&#xff0c;并且计算量…

【二叉树】——

计算布尔二叉树的值 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) …

Spring Boot编程训练系统:构建可扩展的应用

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了编程训练系统的开发全过程。通过分析编程训练系统管理的不足&#xff0c;创建了一个计算机管理编程训练系统的方案。文章介绍了编程训练系统的系统分析部分&…

Java-sec-code-SSRF攻击

Java-sec-code&#xff08;SSRF攻击&#xff09; java-sec-code平台中也内置了SSRF攻击案例&#xff0c;我们来看看SSRF漏洞代码是什么样的。 案例1 直接从url参数接收数据&#xff0c;但是未进行任何检查和校验。 通过调用httpUtil.URLConnection方法&#xff0c;建立URL对…

docker基础:搭建centos7(详见B站泷羽sec)

docker的简单学习&#xff1a; sudo apt-get update //这个命令让系统检查有没有新软件 sudo apt-get install docker.io //安装 Docker sudo docker version //查看是否安装成功&#xff0c;显示docker的版本信息 启用Docker 启…

SwiftUI开发教程系列 - 第十四章:项目实战,创建一个完整的 SwiftUI 应用

在这一章,我们将创建一个简单的任务管理应用,名为 MyTasks,用户可以添加、删除和编辑任务,查看完成状态,并实现数据的本地持久化。应用的核心功能包括: 查看任务列表,支持删除和编辑。添加新任务,支持设置任务标题和完成状态。管理任务状态,可以将任务标记为“完成”或…

RNN(循环神经网络)详解

1️⃣ RNN介绍 前馈神经网络&#xff08;CNN&#xff0c;全连接网络&#xff09;的流程是前向传播、反向传播和参数更新&#xff0c;存在以下不足&#xff1a; 无法处理时序数据&#xff1a;时序数据长度一般不固定&#xff0c;而前馈神经网络要求输入和输出的维度是固定的&a…

解剖C++模板(2) —— 模板匹配规则及特化

众所周知&#xff0c;模板声明部分的尖括号中的内容是声明模板形参&#xff0c;而调用模板时的尖括号是给模板传参。然而这样理解仅仅停留于现象&#xff0c;只是将模板形参传参和函数传参的过程划等号了。C 的函数重载匹配并非真的进行匹配&#xff0c;因为函数名修饰规则导致…

题目讲解15 合并两个排序的链表

原题链接&#xff1a; 合并两个排序的链表_牛客题霸_牛客网 思路分析&#xff1a; 第一步&#xff1a;写一个链表尾插数据的方法。 typedef struct ListNode ListNode;//申请结点 ListNode* BuyNode(int x) {ListNode* node (ListNode*)malloc(sizeof(ListNode));node->…

高性能Web网关:OpenResty 基础讲解

一&#xff1a;概述 OpenResty是由国人章亦春开发的一个基于Nginx的可伸缩的Web平台。 openresty 是一个基于 nginx 与 lua 的高性能 web 平台&#xff0c;其内部集成了大量精良的 lua 库、第三方模块以及大数的依赖项。用于方便搭建能够处理超高并发、扩展性极高的动态 web 应…

OceanBase JDBC (Java数据库连接)的概念、分类与兼容性

本章将介绍 OceanBase JDBC的 概念与分类&#xff0c;已帮助使用 JDBC 的用户及技术人员更好的 了解JDBC&#xff0c;以及 OceanBase JDBC在与 MySQL 及 Oracle 兼容性方面的相关能力。 一、JDBC 基础 1.1 JDBC 的概念 JDBC 一般指 Java 数据库连接。Java 数据库连接&#xf…

关于Django 模型字段 `choices`自定义数据类型的枚举——补充

文章目录 1. 处理 datetime 类型的 choices2. 处理 time 类型的 choices3. 处理 Decimal 类型的 choices4. 处理 UUID 类型的 choices5. 处理 float 类型的 choices 在 Choices 类的基础上扩展&#xff0c;可以将 choices 与特定数据类型&#xff08;如 date 或 datetime&a…

小程序中引入下载到本地的iconfont字体图标加载不出来问题解决

我这个是uniapp项目,字体图标都是一样的,在vue项目中web端、uniapp运行到h5都没问题,但是运行到小程序加载不出来,报错如下: 不让用本地路径,所以我们要转为base64编码,这里给大家提供一个工具,它可以把本地字体文件转为base64:transfonter 进入官网后,第一步: …

如何在 Ubuntu 24.04 上安装和配置 Fail2ban ?

确保你的 Ubuntu 24.04 服务器的安全是至关重要的&#xff0c;特别是如果它暴露在互联网上。一个常见的威胁是未经授权的访问尝试&#xff0c;特别是通过 SSH。Fail2ban 是一个强大的工具&#xff0c;可以通过自动阻止可疑活动来帮助保护您的服务器。 在本指南中&#xff0c;我…

大模型论文精华—20241111

使用大型语言模型进行神经病理诊断的初步探索 研究问题 本研究探讨了大型语言模型&#xff08;LLMs&#xff09;在辅助医生进行神经病理学诊断中的潜在应用。具体来说&#xff0c;研究人员通过设计特定的问题和病例背景&#xff0c;询问多个流行的LLMs&#xff0c;并根据这些…

量化交易系统开发-实时行情自动化交易-3.4.2.2.Okex交易数据

19年创业做过一年的量化交易但没有成功&#xff0c;作为交易系统的开发人员积累了一些经验&#xff0c;最近想重新研究交易系统&#xff0c;一边整理一边写出来一些思考供大家参考&#xff0c;也希望跟做量化的朋友有更多的交流和合作。 接下来聊聊基于Okex交易所API获取交易数…

多商户中英双语电商系统设计与开发 PHP+mysql

随着全球电商市场的扩展&#xff0c;多商户平台成为了越来越多商家参与全球贸易的重要方式。为了适应不同语言用户的需求&#xff0c;尤其是中英双语用户的需求&#xff0c;设计一个支持中英双语的电商系统显得尤为重要。本文将重点探讨如何设计一个多商户中英双语电商系统&…

关于 3D Engine Design for Virtual Globes(三维数字地球引擎设计)

《3D Engine Design for Virtual Globes》是一本专注于三维虚拟地球引擎设计的专业书籍。这本书由Patrick Cozzi和Kevin Ring编写&#xff0c;覆盖了设计适用于虚拟球面环境的三维引擎的各个方面。虚拟地球引擎作为地理信息系统&#xff08;GIS&#xff09;中的一个核心组件&am…

ubuntu 20.04添加ros官方的软件源(解决下载ros软件包出现的E 无法定位软件包的问题)

ubuntu 20.04添加ros官方的软件源&#xff08;解决下载ros软件包出现的E: 无法定位软件包的问题&#xff09; 在 Ubuntu 20.04 上添加 ROS 官方软件源可以解决使用其他镜像源时遇到的 “E: 无法定位软件包” 的问题&#xff0c;主要是因为这些镜像源可能没有同步所有官方提供的…