在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.rhkb.cn/news/471308.html

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

相关文章

ARM架构中断与异常向量表机制解析

往期内容 本专栏往期内容&#xff0c;interrtupr子系统&#xff1a; 深入解析Linux内核中断管理&#xff1a;从IRQ描述符到irq domain的设计与实现Linux内核中IRQ Domain的结构、操作及映射机制详解中断描述符irq_desc成员详解Linux 内核中断描述符 (irq_desc) 的初始化与动态分…

论文翻译 | The Capacity for Moral Self-Correction in Large Language Models

摘要 我们测试了一个假设&#xff0c;即通过人类反馈强化学习&#xff08;RLHF&#xff09;训练的语言模型具有“道德自我纠正”的能力——避免产生有害的输出——如果指示这样做的话。我们在三个不同的实验中发现了支持这一假设的有力证据&#xff0c;每个实验都揭示了道德自…

华为云前台用户可挂载数据盘和系统盘是怎么做到的?

用户可以选择磁盘类型和容量&#xff0c;其后台是管理员对接存储设备 1.管理员如何在后台对接存储设备&#xff08;特指业务存储&#xff09; 1.1FusionSphere CPS&#xff08;Cloud Provisionivice&#xff09;云装配服务 它是first node https://10.200.4.159:8890 对接存…

【Excel】身份证号最后一位“X”怎么计算

大多数人身份证号最后一位都是数字&#xff0c;但有个别号码最后一位却是“X"。 如果你查百度&#xff0c;会得到如下答案&#xff1a; 当最后一位编码是10的时候&#xff0c;因为多出一位&#xff0c;所以就用X替换。 可大多数人不知道的是&#xff0c;这个10是怎么来的…

【常见问题解答】远程桌面无法复制粘贴的解决方法

提示:“奔跑吧邓邓子” 的常见问题专栏聚焦于各类技术领域常见问题的解答。涵盖操作系统(如 CentOS、Linux 等)、开发工具(如 Android Studio)、服务器软件(如 Zabbix、JumpServer、RocketMQ 等)以及远程桌面、代码克隆等多种场景。针对如远程桌面无法复制粘贴、Kuberne…

python解析网页上的json数据落地到EXCEL

安装必要的库 import requests import pandas as pd import os import sys import io import urllib3 import json测试数据 网页上的数据结构如下 {"success": true,"code": "CIFM_0000","encode": null,"message": &quo…

change buffer:到底应该选择普通索引还是唯一索引

文章目录 引言第一章&#xff1a;普通索引和唯一索引在查询逻辑与效率上的对比1.1 查询逻辑分析1.2 查询效率对比 第二章&#xff1a;普通索引和唯一索引在更新逻辑与效率上的对比2.1 更新逻辑分析2.2 更新效率对比 第三章&#xff1a;底层原理详解 - 普通索引和唯一索引的区别…

3D编辑器教程:如何实现3D模型多材质定制效果?

想要实现下图这样的产品DIY定制效果&#xff0c;该如何实现&#xff1f; 可以使用51建模网线上3D编辑器的材质替换功能&#xff0c;为产品3D模型每个部位添加多套材质贴图&#xff0c;从而让3D模型在展示时实现DIY定制效果。 具体操作流程如下&#xff1a; 第1步&#xff1a;上…

git入门环境搭建

git下载 git官网地址&#xff1a;https://git-scm.com/ 如果没有魔法的话&#xff0c;官网这个地址能卡死你 这里给个国内的git镜像链接 git历史版本镜像链接 然后一路next 默认路径 默认勾选就行。 今天就写到这吧&#xff0c;11点多了该睡了&#xff0c;&#xff0c;&#x…

Oracle ADB 导入 BANK_GRAPH 的学习数据

Oracle ADB 导入 BANK_GRAPH 的学习数据 1. 下载数据2. 导入数据运行 setconstraints.sql 1. 下载数据 访问 https://github.com/oracle-quickstart/oci-arch-graph/tree/main/terraform/scripts&#xff0c;下载&#xff0c; bank_accounts.csvbank_txns.csvsetconstraints.…

985研一学习日记 - 2024.11.14

一个人内耗&#xff0c;说明他活在过去&#xff1b;一个人焦虑&#xff0c;说明他活在未来。只有当一个人平静时&#xff0c;他才活在现在。 日常 1、起床6:00 2、健身2h 3、LeetCode刷了题 动态规划概念 如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的…

1.两数之和-力扣(LeetCode)

题目&#xff1a; 解题思路&#xff1a; 在解决这个问题之前&#xff0c;首先要明确两个点&#xff1a; 1、参数returnSize的含义是返回答案的大小&#xff08;数目&#xff09;&#xff0c;由于这里的需求是寻找数组中符合条件的两个数&#xff0c;那么当找到这两个数时&#…

【excel】easy excel如何导出动态列

动态也有多重含义&#xff1a;本文将描述两种动态场景下的解决方案 场景一&#xff1a;例如表头第一列固定为动物&#xff0c;且必定有第二列&#xff0c;第二列的表头可能为猫 也可能为狗&#xff1b;这是列数固定&#xff0c;列名不固定的场景&#xff1b; 场景二&#xff1…

〔 MySQL 〕数据类型

目录 1.数据类型分类 2 数值类型 2.1 tinyint类型 2.2 bit类型 2.3 小数类型 2.3.1 float 2.3.2 decimal 3 字符串类型 3.1 char 3.2 varchar 3.3 char和varchar比较 4 日期和时间类型 5 enum和set mysql表中建立属性列&#xff1a; 列名称&#xff0c;类型在后 n…

LlamaIndex

一、大语言模型开发框架 SDK:Software Development Kit,它是一组软件工具和资源的集合,旨在帮助开发者创建、测试、部署和维护应用程序或软件。 所有开发框架(SDK)的核心价值,都是降低开发、维护成本。 大语言模型开发框架的价值,是让开发者可以更方便地开发基于大语言…

【FFmpeg】FFmpeg 函数简介 ③ ( 编解码相关函数 | FFmpeg 源码地址 | FFmpeg 解码器相关 结构体 和 函数 )

文章目录 一、FFmpeg 解码器简介1、解码流程分析2、FFmpeg 编解码器 本质3、FFmpeg 编解码器 ID 和 名称 二、FFmpeg 解码器相关 结构体 / 函数1、AVFormatContext 结构体2、avcodec_find_decoder 函数 - 根据 ID 查找 解码器3、avcodec_find_decoder_by_name 函数 - 根据 名称…

Linux——GPIO输入输出裸机实验

学习了正点原子Linux环境下的GPIO的输入输出的裸机实验学习&#xff0c;现在进行一下小结&#xff1a; 启动文件start.S的编写 .global _start .global _bss_start _bss_start:.word __bss_start.global _bss_end _bss_end:.word __bss_end_start:/*设置处理器进入SVC模式*/m…

zabbix搭建钉钉告警流程

目录 &#x1f324;️zabbix实验规划 &#x1f324;️zabbix实验步骤 &#x1f4d1;1 使用钉钉添加一个自定义的机器人 ​ &#x1f4d1;2在zabbix-server上编写钉钉信息发送脚本&#xff0c;设置钉钉报警媒介 ☁️ 设置钉钉报警媒介​编辑​编辑 ☁️在添加消息模板​编辑​…

Java 多线程(三)—— 死锁

死锁的产生 我们先从简单的死锁最后到难一些的死锁问题开始展开讨论。 首先一个线程&#xff0c;一把锁&#xff0c;因为多次加锁而导致死锁问题&#xff0c;由于Java 的synchronized 实现了可重入锁&#xff0c;因此这个死锁问题就不存在了&#xff0c;意味着当一个线程拥有…

makefile 设置动态库路径参数

目录 一、makefile 动态库相关1.1 Libs 变量1.2 LDFLAGS 变量1.3 二者的作用和区别 二、设置方式2.1 编译时指定库路径2.2 运行时指定库路径 三、测试 一、makefile 动态库相关 1.1 Libs 变量 在 Makefile 中&#xff0c;Libs 通常是一个变量&#xff0c;用于存储链接器&…