• 防止使用春季休眠的数据库事务中的更新丢失


    什么是丢失更新?

    当两个并发事务同时更新相同的数据库记录/列时,导致第一个更新被第二个事务以静默方式覆盖。数据库中的这种现象被称为经典的更新丢失问题。以下是丢失更新时的事件顺序-

    典型丢失更新方案的事件序列
    JIRA的空中碰撞

    当两个人尝试更新 JIRA 中的相同 Bug 时,会发生空中碰撞,JIRA 会优雅地处理它。JIRA的程序员知道如何防止丢失更新!

    处理此场景的方法主要有两种:

    1. 乐观锁定

    2. 悲观锁定

    我们将通过适当的示例一一讨论这两种方法,

    乐观锁定方法

    所有用户/线程都可以同时读取数据,但是当多个线程尝试同时更新数据时,第一个线程将获胜,而所有其他线程将因 OptimisticLockException 而失败,他们必须再次尝试执行更新。因此,即使在并发使用的情况下,也不会静默丢失任何更新。

    它是如何工作的?

    数据库中的每条记录都维护一个版本号。当第一个事务从数据库读取记录时,它也会收到版本。修改后,服务器将记录的版本号与数据库中的版本号进行比较,如果未更改,则使用递增的版本号写入记录。

    然后,第二笔交易将以相同的记录编号进入(即两个客户端都在更新该记录)。这一次,服务器将识别出数据库中的版本号在第一次事务中已更改,并拒绝更新。

    JPA 乐观锁定允许任何人读取和更新实体,但是在提交时会进行版本检查,如果自上次读取实体以来在数据库中更新了版本,则会引发异常。

    如何在 JPA 中启用乐观锁定?

    要在 JPA 中为实体启用乐观锁定,只需对属性进行批注,如下面的代码示例所示@Version

    使用长属性进行版本控制(首选方法)
    1. @Entity
    2. @Table (name="t_flight")
    3. public class Flight {
    4. @ID
    5. @GeneratedValue (strategy=GenerationType.AUTO)
    6. private int id;
    7. @Version
    8. private long version;
    9. ...
    10. }
    只需两行代码即可在 JPA 中启用乐观锁定。

    请务必注意,只有短、整型、长和时间戳字段才能使用 @Version 属性进行批注。

    时间戳是一种不如版本号可靠的乐观锁定方式,但应用程序也可以将其用于其他目的。

    使用时间戳进行乐观锁定(不应首选)
    1. @Entity
    2. @Table ( name = "t_flight" )
    3. public class Flight implements Serializable {
    4. ...
    5. @Version
    6. public Date getLastUpdate() { ... }
    7. }

    引擎盖下

    在后台,JPA 将在每次成功提交时递增 Version 属性。

    这会导致 SQL 如下所示(请注意,JPA 处理所有内容,其显示仅用于说明目的):

    UPDATE Flight SET ..., version = version + 1 WHERE id = ? AND version = readVersion

    乐观方法的利弊

    1. 乐观锁定的优点是没有数据库锁,这可以提供更好的可伸缩性。

    2. 缺点是用户或应用程序必须刷新并重试失败的更新。

    悲观锁定方法

    使用悲观锁定时,hibernate 会锁定记录供您独占使用,直到您提交事务。这通常是在数据库级别使用语句实现的。在这种情况下,任何其他尝试更新/访问同一记录的事务都将被阻止,直到第一个事务释放锁。SELECT …​ FOR UPDATE

    此策略以性能为代价提供更好的可预测性,并且扩展性不大。在内部,它的行为类似于所有线程(读取和写入)对单个记录的顺序访问,这就是可扩展性是一个问题的原因。

    通常,如果只是在事务级别(可序列化)指定适当的隔离级别,数据库将自动为您处理它。Hibernate为您提供了在新事务开始时获得独占悲观锁的选项。以下锁定模式可用于在休眠会话中获取悲观锁定。

    锁定模式升级

    使用支持该语法的数据库根据显式用户请求获取。SELECT …​ FOR UPDATE

    LockMode.UPGRADE_NOWAIT

    根据明确的用户请求使用 ain Oracle 获取。SELECT …​ FOR UPDATE NOWAIT

    请注意,使用上述 UPGRADE 模式,您希望修改加载的对象,并且在提交事务之前不希望任何其他线程在您在此过程中更改它。

    如何在 Spring Data JPA & Hibernate 中启用悲观锁定

    使用 Spring 数据 JPA 的悲观锁定示例
    1. interface Flight extends Repository {
    2. @Lock(LockModeType.PESSIMISTIC_WRITE)
    3. Flight findOne(Long id);
    4. }
    从 Spring Data JPA 的 1.6 版开始,CRUD 方法支持@Lock,一旦您提交事务,锁就会自动释放。
    使用 Spring & Hibernate 的悲观锁定
    1. tx.begin();
    2. Flight w = em.find(Flight.class, 1L, LockModeType.PESSIMISTIC_WRITE);
    3. w.decrementBy(4);
    4. em.flush();
    5. tx.commit();

    事务隔离级别和锁定模式有什么区别?

    隔离级别会影响您看到的内容。 锁定模式会影响允许您执行的操作。

    为什么不在 Java 级别而不是数据库/休眠级别处理并发?

    当我们使用支持事务的数据库时,并发管理必须远离Java代码,而应该由数据库事务隔离级别和锁定策略来处理。造成这种情况的主要原因是——

    1. 它使我们的代码更简单,在数据库级别处理并发更容易。

    2. 如果在 JVM 级别管理并发性,则应用程序在移动到多个分布式 JVM 时将中断,因此解决方案将永远无法扩展。另一方面,数据库是单点入口,即使多个 JVM 正在调用并行请求,也可以处理并发。

    3. 即使您只有单个 JVM 设置,与您自己的手工编织同步 Java 代码相比,乐观锁定也可能产生更高的性能。

  • 相关阅读:
    1.什么是jwt?jwt的作用是什么?2.jwt的三个部分是什么?三者之间的关系如何?3.JWT运行的流程是什么
    新思路,4.9+氧化应激相关基因构建风险模型
    Ubuntu服务器下安装FastDFS及nginx配置访问等问题记录
    拜托,使用Three.js让二维图片具有3D效果超酷的好吗 💥
    MySQL高级SQL语句
    gnss、gps、imu、rtk、ins区分及含义
    机器人制作开源方案 | 智能快递付件机器人
    js 锚点定位的方法
    Doris删库元数据删除怎么办?紧急恢复单副本情况
    分布式计算MapReduce | Spark实验
  • 原文地址:https://blog.csdn.net/allway2/article/details/127673652