我花了整个上午阅读所有关于Google churns up on optimistic locking的热门文章,而对于我的一生,我仍然一无所知。
我了解乐观锁定涉及添加一列来跟踪记录的“版本”,并且该列可以是时间戳记,计数器或任何其他版本跟踪构造。但是我仍然不了解如何确保WRITE完整性(这意味着,如果多个进程同时更新同一实体,那之后,该实体将正确反射(reflect)其应处于的真实状态)。
有人可以提供具体的,易于理解的示例,说明如何在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
表的情况吗?基本上,我希望看到行动中的乐观锁定,并对其操作原理进行简单易懂的解释。 最佳答案
通常,当您考虑乐观锁定时,还可以使用像Hibernate之类的库或其他具有 @Version
支持的JPA-Implementation。
示例可能如下所示:
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
的哪种列类型用作版本?
如果许多不同的应用程序访问您的数据,则最好使用数据库自动更新的列。例如对于MySQL
version TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
这样,实现乐观锁定的应用程序将注意到哑应用程序的更改。
如果您更新实体的频率超过了
TIMESTAMP
的分辨率或Java解释的频率,则此方法可能无法检测到某些更改。另外,如果让Java生成新的TIMESTAMP
,则需要确保所有运行您的应用程序的机器都处于完美的时间同步状态。如果所有应用程序都可以更改为整数,则长,...版本通常是一个好的解决方案,因为它永远不会受到时钟设置不同的困扰;-)
还有其他方案。您可以例如每次更改行时,都使用哈希,甚至随机生成
String
。重要的是,在任何进程保存用于本地处理的数据或在缓存内部时,不要重复值,因为该进程将无法通过查看版本列来检测更改。作为最后的选择,您可以将所有字段的值用作版本。尽管这在大多数情况下将是最昂贵的方法,但它是在不更改表结构的情况下获得相似结果的一种方法。如果使用Hibernate,则使用
@OptimisticLocking
-annotation强制执行此行为。如果由于您已读取实体而导致任何行更改,则在实体类上使用@OptimisticLocking(type = OptimisticLockType.ALL)
失败,或者在另一个进程也更改了您更改的字段时,@OptimisticLocking(type = OptimisticLockType.DIRTY)
失败。