具体(Java)示例的乐观锁定

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21120043/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 06:44:25  来源:igfitidea点击:

Optimistic Locking by concrete (Java) example

javaconcurrencylockingoptimistic-locking

提问by AdjustingForInflation

I have spent my morning reading all the top articles that Google churns up on optimistic locking, and for the life of me, I still don't really get it.

我花了一上午的时间阅读谷歌关于乐观锁定的所有顶级文章,但在我的一生中,我仍然没有真正理解它。

I understandthat optimistic locking involves the addition of a column for tracking the record's "version", and that this column can be a timestamp, a counter, or any other version-tracking construct. But I'm still not understanding how that ensures WRITE integrity (meaning that if multiple process are updating the same entity at the same time, that afterwards, the entity correctly reflects the true state it should be in).

知道乐观锁定涉及添加用于跟踪记录“版本”的列,并且该列可以是时间戳、计数器或任何其他版本跟踪构造。但是我仍然不明白这如何确保 WRITE 完整性(意味着如果多个进程同时更新同一个实体,那么该实体之后会正确反映它应该处于的真实状态)。

Can someone provide a concrete, easy-to-understandexample of how optimistic locking could be used in Java (against, perhaps, a MySQL DB). Let's say we have a Personentity:

有人可以提供一个具体的、易于理解的示例,说明如何在 Java 中使用乐观锁定(可能是针对 MySQL 数据库)。假设我们有一个Person实体:

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private Color favoriteColor;
}

And that Personinstances get persisted to a peopleMySQL table:

并且该Person实例被持久化到peopleMySQL 表中:

CREATE TABLE people (
    person_id PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,        # } I realize these column defs are not valid but this is just pseudo-code
    age INT NOT NULL,
    color_id FOREIGN KEY (colors) NOT NULL  # Say we also have a colors table and people has a 1:1 relationship with it
);

Now let's say there are 2 software systems, or 1 system with 2 threads on it, that are trying to update the same Personentity at the same time:

现在假设有 2 个软件系统,或者 1 个有 2 个线程的系统,它们试图同时更新同一个Person实体:

  • Software/Thread #1 is trying to persist a surname change (from "John Smith" to "John Doe")
  • Software/Thread #2 is trying to persist a change in the favorite color (from REDto GREEN)
  • 软件/线程 #1 试图保留姓氏更改(从“ John Smith”到“ John Doe”)
  • 软件/线程 #2 试图保持最喜欢的颜色的变化(从REDGREEN

My questions:

我的问题:

  1. How could optimistic locking be implemented on the peopleand/or colorstables? (Looking for specific DDL example)
  2. How could you then utilize this optimistic locking at the application/Java layer? (Looking for specific code example)
  3. Can someone run me through a scenario where the DDL/code changes (from #1 and #2 above) would come into play in my scenario (or any other scenario) and would "optimistically lock" the people/colorstables correctly? Basically, I'm looking to see optimistic locking in action, with an easy-to-follow explanation of why it works.
  1. 如何在people和/或colors表上实现乐观锁定?(寻找特定的 DDL 示例)
  2. 那么您如何在应用程序/Java 层使用这种乐观锁呢?(寻找具体的代码示例)
  3. 有人可以让我完成一个场景,其中 DDL/代码更改(来自上面的 #1 和 #2)将在我的场景(或任何其他场景)中发挥作用,并且会正确地“乐观地锁定” people/colors表?基本上,我希望看到乐观锁定的作用,并对其工作原理进行简单易懂的解释。

回答by TheConstructor

Normally when you look into optimistic locking you also use a library like Hibernateor an other JPA-Implementation with @Versionsupport.

通常,当您研究乐观锁时,您还会使用像Hibernate这样的库或其他@Version支持的JPA 实现。

Example could read like this:

示例可以是这样的:

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private Color favoriteColor;
    @Version
    private Long version;
}

while obviously there is no point of adding a @Versionannotation if you are not using a framework which supports this.

虽然@Version如果您不使用支持此功能的框架,显然没有必要添加注释。

The DDL could then be

然后 DDL 可以是

CREATE TABLE people (
    person_id PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,        # } I realize these column defs are not valid but this is just pseudo-code
    age INT NOT NULL,
    color_id FOREIGN KEY (colors) NOT NULL,  # Say we also have a colors table and people has a 1:1 relationship with it
    version BIGINT NOT NULL
);

What happens with the version?

版本会怎样?

  1. Every time before you store the entity, you check if the version stored in the database is still the version you know.
  2. If it is, store your data with version incremented by one
  1. 每次存储实体之前,您都会检查存储在数据库中的版本是否仍然是您知道的版本。
  2. 如果是,则以版本递增一存储您的数据

To get both steps done without risking an other process changing data between both steps it is normally handled through a statement like

为了在不冒其他进程在两个步骤之间更改数据的风险的情况下完成这两个步骤,通常通过以下语句进行处理

UPDATE Person SET lastName = 'married', version=2 WHERE person_id = 42 AND version = 1;

After executing the statement you check if you updated a row or not. If you did, nobody else changed the data since you've read it, otherwise somebody else changed the data. If somebody else changed the data you will normally receive an OptimisticLockExceptionby the library you are using.

执行语句后,您检查是否更新了一行。如果您这样做了,那么自从您阅读数据以来,没有其他人更改过数据,否则其他人更改了数据。如果其他人更改了数据,您通常会收到OptimisticLockException您正在使用的库的数据。

This exception should cause all changes to be revoked and the process of changing the value to be restarted as the condition upon which the entity was to be updated may no longer be applicable.

此异常应导致撤销所有更改,并重新启动更改值的过程,因为更新实体的条件可能不再适用。

So no collision:

所以没有碰撞:

  1. Process A reads Person
  2. Process A writes Person thereby incrementing version
  3. Process B reads Person
  4. Process B writes Person thereby incrementing version
  1. 进程 A 读取人员
  2. 进程 A 写入 Person 从而增加版本
  3. 进程 B 读取 Person
  4. 进程 B 写入 Person 从而增加版本

Collision:

碰撞:

  1. Process A reads Person
  2. Process B reads Person
  3. Process A writes Person thereby incrementing version
  4. Process B receives an exception when trying to save as the version changed since Person was read
  1. 进程 A 读取人员
  2. 进程 B 读取 Person
  3. 进程 A 写入 Person 从而增加版本
  4. 进程 B 在尝试保存时收到异常,因为自读取 Person 以来版本已更改

If Colour is another object you should put a version there by the same scheme.

如果 Color 是另一个对象,您应该按照相同的方案在那里放置一个版本。

What isn't Optimistic Locking?

什么不是乐观锁?

  • Optimistic Locking is no magic to merge conflicting changes. Optimistic Locking will just prevent processes from accidentally overwriting changes by another process.
  • Optimistic Locking actually is no real DB-Lock. It just works by comparing the value of the version column. You don't prevent other processes from accessing any data, so expect that you get OptimisticLockExceptions
  • 乐观锁定并不是合并冲突更改的魔法。乐观锁定只会防止进程意外覆盖另一个进程的更改。
  • 乐观锁实际上并不是真正的 DB-Lock。它只是通过比较 version 列的值来工作。你不会阻止其他进程访问任何数据,所以期望你得到OptimisticLockExceptions

What column-type to use as version?

什么列类型用作版本?

If many different applications access your data you may be best off using a column automatically updated by the database. e.g. for MySQL

如果许多不同的应用程序访问您的数据,最好使用由数据库自动更新的列。例如对于 MySQL

version TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;

this way the applications implementing optimistic locking will notice changes by dumb applications.

这样,实现乐观锁定的应用程序将注意到愚蠢应用程序的变化。

If you update entities more often than the resolution of TIMESTAMPor the Java-interpretation of it, ths approach can fail to detect certain changes. Also if you let Java generate the new TIMESTAMPyou need to ensure that all machines running your applications are in perfect time-sync.

如果您更新实体的频率高于它的解析TIMESTAMP或 Java 解释,则该方法可能无法检测到某些更改。此外,如果您让 Java 生成新的,TIMESTAMP您需要确保运行您的应用程序的所有机器都处于完美的时间同步。

If all of your applications can be altered an integer, long, ... version is normally a good solution as it will never suffer from differently set clocks ;-)

如果您的所有应用程序都可以更改整数,长整数,...版本通常是一个很好的解决方案,因为它永远不会受到不同设置时钟的影响;-)

There are other scenarios. You could e.g. use a hash or even randomly generate a Stringevery time a row is to be changed. Important is, that you don't repeat values while any process is holding data for local processing or inside a cache as that process will not be able to detect change by looking at the version-column.

还有其他场景。例如,您可以在String每次更改行时使用散列或什至随机生成一个。重要的是,当任何进程为本地处理或缓存保存数据时,您不要重复值,因为该进程将无法通过查看版本列来检测更改。

As a last resort you may use the value of all fields as version. While this will be the most expensive approach in most cases it is a way to get similar results without changing the table structure. If you use Hibernate there is the @OptimisticLocking-annotation to enforce this behavior. Use @OptimisticLocking(type = OptimisticLockType.ALL)on the entity-class to fail if any row changed since you have read the entity or @OptimisticLocking(type = OptimisticLockType.DIRTY)to just fail when another process changed the fields you changed, too.

作为最后的手段,您可以使用所有字段的值作为版本。虽然在大多数情况下这将是最昂贵的方法,但它是一种无需更改表结构即可获得类似结果的方法。如果您使用 Hibernate,则可以使用@OptimisticLocking-annotation 来强制执行此行为。@OptimisticLocking(type = OptimisticLockType.ALL)在实体类上使用,如果任何行在您读取实体后@OptimisticLocking(type = OptimisticLockType.DIRTY)发生更改,则失败,或者在另一个进程更改您更改的字段时失败。

回答by no comprende

@TheConstructor: Great explanation of what it is and is not. When you said "Optimistic Locking is no magic to merge conflicting changes" I wanted to comment. I used to manage a DataFlex application which allowed users to edit records on a form. When they pushed the "Save" button, the app would do what was called a "multi-user re-read" of the data - pulling the current values - and compare to what the user had modified. If the fields the user modified had not been changed in the meantime, then just those fields would be written back to the record (which was locked only during the re-read + write operation) and therefore, 2 users could transparently modify different fields on the same record with no issues. It did not require version stamps, just a knowledge of which fields were modified.

@TheConstructor:对它是什么和不是什么的很好的解释。当你说“乐观锁定不是合并冲突更改的魔法”时,我想评论一下。我曾经管理过一个 DataFlex 应用程序,它允许用户编辑表单上的记录。当他们按下“保存”按钮时,应用程序将执行所谓的“多用户重新读取”数据——提取当前值——并与用户修改的内容进行比较。如果用户修改的字段在此期间没有被更改,那么只会将这些字段写回记录(仅在重新读取 + 写入操作期间被锁定),因此,2 个用户可以透明地修改不同的字段没有问题的相同记录。它不需要版本戳,只需要知道哪些字段被修改。

Granted, this is not a perfect solution, but it did the job in that case. It was optimistic and it did allow unrelated changes and it gave the user an error on conflicting changes. This was the best that could be done, pre-SQL, but is still a good design principle today, perhaps for more object- or web-related scenarios.

诚然,这不是一个完美的解决方案,但它在那种情况下完成了工作。它是乐观的,它确实允许不相关的更改,并且在发生冲突的更改时给用户一个错误。这是可以做到的最好的,pre-SQL,但今天仍然是一个很好的设计原则,也许适用于更多与对象或 Web 相关的场景。