我早上都在阅读Google churns up on optimistic locking的所有热门文章,而对于我的生活,我仍然没有真正理解它。
我理解乐观锁定涉及添加用于跟踪记录的“版本”的列,并且该列可以是时间戳,计数器或任何其他版本跟踪构造。但我仍然不明白如何确保WRITE完整性(意味着如果多个进程同时更新同一个实体,那么之后,实体正确地反映了它应该处于的真实状态)。
有人可以提供一个具体,易于理解的示例,说明如何在Java中使用乐观锁定(可能是MySQL数据库)。假设我们有一个Person
实体:
public class Person {
private String firstName;
private String lastName;
private int age;
private Color favoriteColor;
}
并且Person
个实例被持久化到people
MySQL表:
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
);
现在假设有2个软件系统或1个系统,其上有2个线程,它们试图同时更新同一个Person
实体:
people
和/或colors
表上实施乐观锁定? (寻找特定的DDL示例)people
的情况/ colors
表是否正确?基本上,我希望看到乐观锁定在行动中,并简单地解释它为何起作用。答案 0 :(得分:37)
通常,当您考虑乐观锁定时,您还可以使用Hibernate之类的库或其他具有@Version
支持的JPA实施。
示例可以如下所示:
public class Person {
private String firstName;
private String lastName;
private int age;
private Color favoriteColor;
@Version
private Long version;
}
虽然如果你没有使用支持它的框架,显然没有必要添加@Version
注释。
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
);
要完成这两个步骤而不冒其他进程在两个步骤之间更改数据的风险,通常会通过类似
的语句来处理UPDATE Person SET lastName = 'married', version=2 WHERE person_id = 42 AND version = 1;
执行语句后,检查是否更新了一行。如果你这样做了,那么没有其他人在你阅读之后就改变了数据,否则其他人就改变了数据。如果其他人更改了数据,您通常会收到正在使用的库的OptimisticLockException
。
此异常应导致所有更改都被撤销,并且更改要重新启动的值的过程可能不再适用于更新实体的条件。
所以没有碰撞:
碰撞:
如果Color是另一个对象,你应该用相同的方案放置一个版本。
OptimisticLockException
s 如果许多不同的应用程序访问您的数据,您最好使用数据库自动更新的列。例如对于MySQL
version TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
这样,实现乐观锁定的应用程序会注意到哑应用程序的变化。
如果更新实体的频率高于TIMESTAMP
的分辨率或Java解释,则该方法无法检测到某些更改。此外,如果您让Java生成新的TIMESTAMP
,您需要确保运行应用程序的所有计算机都处于完美的时间同步状态。
如果您的所有应用程序都可以更改整数,长,...版本通常是一个很好的解决方案,因为它永远不会受到不同设置时钟的影响; - )
还有其他方案。你可以,例如每次要更改行时,使用哈希值甚至随机生成String
。重要的是,当任何进程持有本地处理数据或缓存内部时,您不会重复值,因为该进程无法通过查看版本列来检测更改。
作为最后的手段,您可以使用所有字段的值作为版本。虽然这在大多数情况下是最昂贵的方法,但它是一种在不改变表结构的情况下获得类似结果的方法。如果您使用Hibernate,则会有@OptimisticLocking
- 注释来强制执行此行为。如果在读取实体后任何行发生更改,则对实体类使用@OptimisticLocking(type = OptimisticLockType.ALL)
会失败,或者当另一个进程更改您更改的字段时,@OptimisticLocking(type = OptimisticLockType.DIRTY)
只会失败。
答案 1 :(得分:3)
@TheConstructor:很好地解释它是什么,不是什么。当你说"乐观锁定并不是合并冲突变化的魔力。我想发表评论。我曾经管理过DataFlex应用程序,它允许用户编辑表单上的记录。当他们推动" Save"按钮,该应用程序将执行所谓的" 多用户重读"数据 - 拉动当前值 - 并与用户修改的内容进行比较。如果用户修改的字段在此期间没有被更改,那么只有那些字段将被写回到记录(仅在重新读取+写入操作期间被锁定),因此,2个用户可以透明地修改不同的字段。同样的记录没有问题。它不需要版本标记,只需要知道修改了哪些字段。
当然,这不是一个完美的解决方案,但在这种情况下它完成了工作。这是乐观的,它确实允许不相关的更改,它给用户一个错误的冲突更改。这是最好的,可以在SQL之前完成,但今天仍然是一个很好的设计原则,可能适用于更多与对象或网络相关的场景。