在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.xdnf.cn/news/13550.html

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

相关文章

机器学习—多个输出的分类(Optional)

有一种不同类型的分类问题&#xff0c;称为多标签分类问题&#xff0c;与每个图像相关联的地方可能有多个标签。 如果你正在制造一辆自动驾驶汽车或者驾驶辅助系统&#xff0c;然后给你一张车前的照片&#xff0c;你可能想问&#xff0c;比如有没有一辆车或者至少有一辆车还是…

上海市计算机学会竞赛平台2020年4月月赛丙组永恒的生命游戏

题目背景 2020年4月11日&#xff0c;英国数学家 约翰霍顿康威&#xff08;John Horton Conway&#xff09;因为新型冠状病毒肺炎不幸逝世。他在群论、数论、代数、几何拓扑、理论物理、组合博弈论和几何等领域&#xff0c;都做出了重大贡献。他的离去是人类文明的损失。他最著…

SQLI LABS | Less-43 POST-Error Based-String-Stacked With Twist

关注这个靶场的其它相关笔记&#xff1a;SQLI LABS —— 靶场笔记合集-CSDN博客 0x01&#xff1a;过关流程 输入下面的链接进入靶场&#xff08;如果你的地址和我不一样&#xff0c;按照你本地的环境来&#xff09;&#xff1a; http://localhost/sqli-labs/Less-43/ 本关是堆…

UEFI Shell命令(二)

一、Shell 命令行选项 ​-b, -break 每页输出后暂停一会&#xff0c;即分页输出 -q, -quiet 抑制所有的输出 -sfo 标准格式输出 -t, -terse 简洁的输出 -v, -verbose 详细的输出 -&#xff1f; 帮助 二、特殊Shell命令 1、attrib 显示或更改文件或目录的属性 [a | -a] 设置…

【QT常用技术讲解】优化网络链接不上导致qt、qml界面卡顿的问题

前言 qt、qml项目经常会涉及访问MySQL数据库、网络服务器&#xff0c;并且界面打开时的初始化过程就会涉及到链接Mysql、网络服务器获取数据&#xff0c;如果网络不通&#xff0c;卡个几十秒&#xff0c;会让用户觉得非常的不爽&#xff0c;本文从技术调研的角度讲解解决此类问…

【C语言】程序性能优化——除法运算符

【C语言】程序性能优化——除法运算符 文章目录 [TOC](文章目录) 前言一、牛顿迭代法1、数学基础2、C代码3、实验 二、二分法1、数学基础2、C代码3、实验 三、参考资料总结 前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、牛顿迭代法 1、数学…

每日计划-1109

1. 完成 104. 二叉树的最大深度 class Solution { public:// 计算二叉树的最大深度的函数int maxDepth(TreeNode* root) {// 如果根节点为空&#xff0c;说明已经到达叶子节点的下一层&#xff0c;返回0&#xff08;这里代码中 return false 应该是错误的&#xff0c;应该是 r…

基于YOLOv5的人群密度检测系统设计与实现

大家好&#xff0c;本文将介绍基于改进后的YOLOv5目标检测模型&#xff0c;设计并实现人群密度检测系统。 使用YOLOv5的源代码&#xff0c;在此基础上修改和训练模型&#xff0c; 数据集选用crowdhuman数据集。对yolov5源码中的文件进行修改&#xff0c;更换主干网络、改进损失…

鸿蒙入门——ArkUI 自定义组件间的父子双向同步状态装饰器@Link语法(四)

文章大纲 引言一、组件间状态装饰器Link 父子双向同步1、使用规则2、支持的观察变化的场景和ArkUI 刷新UI3、Link变量值初始化和更新机制3.1、初始渲染&#xff1a;执行父组件的build()函数后将创建子组件的新实例。3.2、Link的数据源的更新&#xff1a;即父组件中状态变量更新…

【Android、IOS、Flutter、鸿蒙、ReactNative 】启动页

Android 设置启动页 自定义 splash.xml 通过themes.xml配置启动页背景图 IOS 设置启动页 LaunchScreen.storyboard 设置为启动页 storyboard页面绘制 Assets.xcassets 目录下导入图片 AppLogo Flutter 设置启动页 Flutter Android 设置启动页 自定义 launch_background.xm…

[SaaS] 数禾科技 AIGC生成营销素材

https://zhuanlan.zhihu.com/p/923637935https://zhuanlan.zhihu.com/p/923637935

前端开发中常用的包管理器(npm、yarn、pnpm、bower、parcel)

文章目录 1. npm (Node Package Manager)2. Yarn (Yarn Package Manager)3. pnpm4. Bower5. Parcel总结 前端开发中常用的包管理器主要有以下几个&#xff1a; 1. npm (Node Package Manager) 简介&#xff1a; npm 是 Node.js 的默认包管理器&#xff0c;也是最广泛使用的包…

【go从零单排】Random Numbers、Number Parsing

&#x1f308;Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 &#x1f4d7;概念 这里是引用 &#x1f4bb;代码 Random Numbers package mainimport ("fmt…

qt移植到arm报错动态库找不到

error while loading shared libraries: libAlterManager.so.1: cannot open shared object file: No such file or directory 通过设置环境变量 LD_LIBRARY_PATH就行了。 LD_LIBRARY_PATH是一个用于指定动态链接器在运行时搜索共享库的路径的环境变量。 例如&#xff1a; 前…

GoogleMIT:多智能体医疗决策框架MDAgents

|记昨日与国内某Top 1&2 医院科室老师及团队探讨技术、医学、信仰与责任而有感而发。 生成式基础大模型正在成为临床辅助甚至医学探索领域的宝贵工具。尽管我们在国内看到了很多企业或实验室联合医疗机构在如医疗记录生成、临床表型辅助诊疗、医疗知识问答交互、医院管理决…

【数据库】深入解析慢 SQL 的识别与优化策略

文章目录 什么是慢 SQL&#xff1f;慢 SQL 的危害如何检测分析慢 SQL使用 MySQL 慢查询日志利用 EXPLAIN 分析执行计划通过 Profiling 获取详细执行信息借助慢 SQL 收集分析平台 实际案例解析&#xff1a;600秒的慢 SQL 优化之旅问题描述初步分析优化步骤1. 优化 SQL 语句结构2…

高校大数据人工智能教学沙盘分享

大数据教学实训沙盘&#xff08;TipDM-SP&#xff09;是根据企业实际项目建设而成&#xff0c;并提供沙盘配套装置、软件以及教学实训资源。沙盘的作用主要有3个&#xff1a; 1、采集真实数据&#xff0c;解决教学中缺少真实数据的困扰&#xff1b; 2、形成从数据…

【C++】string模拟实现

各位读者老爷好&#xff0c;俺最近在学习string的一些知识。为了更好的了解string的结构&#xff0c;俺模拟实现了一个丐版string&#xff0c;有兴趣的老爷不妨垂阅&#xff01;&#xff01;&#xff01; 目录 1.string类的定义 2.模拟实现成员函数接口 2.1.constructor&am…

c_str()函数 string类型转换成char*类型 C++实现

问题&#xff1a;在 class 的构造函数中&#xff0c;如果我们在类中初始化了 char * 类型&#xff0c;在调用构造函数时&#xff0c;如果直接传入字符串( string )类型&#xff0c;编译器会提出如下警告&#xff1a; 想要消除这个警告&#xff0c;就需要将 string 类型的变量转…

【vue3文件上传同时出现两个提示框,一个提示成功,一个提示失败,一个是用写死的,一个是接口返回的】

文件上传同时出现两个提示框&#xff0c;一个提示成功&#xff0c;一个提示失败&#xff0c;一个是用写死的&#xff0c;一个是接口返回的 原因&#xff1a; 接口返回的是字符串code200" 把判断的code码改为字符串的就好了