java Spring 数据 - 启用乐观锁定
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/47281523/
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
Spring data - enable optimistic locking
提问by vratojr
Note: I DON'T NEED AN EXPLANATION CONCERNING THE OPTIMISTIC LOCKING. This question is about what seems to be a specific Spring Data behavior when using optimistic locking.
注意:我不需要关于乐观锁定的解释。这个问题是关于在使用乐观锁定时似乎是特定的 Spring Data 行为。
From the jpa specswhenever an entity has a @Version
annotated field, optimistic locking should be enabled automatically on the entity.
根据 jpa规范,只要实体具有带@Version
注释的字段,就应该在实体上自动启用乐观锁定。
If I do this in a spring data test project using Repositories, the locking seems to not be activated. Infact no OptimisticLockException
is thrown while doing a Non Repetable Read test (see P2 on page 93 of the JPA specs)
如果我在使用 Repositories 的 spring 数据测试项目中执行此操作,则锁定似乎未激活。事实上OptimisticLockException
,在进行不可重复读取测试时会抛出no (参见 JPA 规范第 93 页上的 P2)
However, from spring docsI see that if we annotate a single method with @Lock(LockModeType.OPTIMISTIC)
then the underlying system correctly throws an OptimisticLockException
(that is then catch by spring and propagated up the stack in a slightly different form).
但是,从 spring文档中我看到,如果我们使用单个方法注释,@Lock(LockModeType.OPTIMISTIC)
那么底层系统会正确抛出一个OptimisticLockException
(然后被 spring 捕获并以稍微不同的形式向上传播)。
Is this normal or did I miss something? Are we obliged to annotate all our methods (or to create a base repository implementation that takes the lock) to have optimistic behavior enabled with spring data?
这是正常的还是我错过了什么?我们是否有义务注释我们所有的方法(或创建一个获取锁的基本存储库实现)以使用 spring 数据启用乐观行为?
I'm using spring data in the context of a spring boot project, version 1.4.5.
我在 spring boot 项目 1.4.5 版的上下文中使用 spring 数据。
The test:
考试:
public class OptimisticLockExceptionTest {
static class ReadWithSleepRunnable extends Thread {
private OptimisticLockExceptionService service;
private int id;
UserRepository userRepository;
public ReadWithSleepRunnable(OptimisticLockExceptionService service, int id, UserRepository userRepository) {
this.service = service;
this.id = id;
this.userRepository = userRepository;
}
@Override
public void run() {
this.service.readWithSleep(this.userRepository, this.id);
}
}
static class ModifyRunnable extends Thread {
private OptimisticLockExceptionService service;
private int id;
UserRepository userRepository;
public ModifyRunnable(OptimisticLockExceptionService service, int id, UserRepository userRepository) {
this.service = service;
this.id = id;
this.userRepository = userRepository;
}
@Override
public void run() {
this.service.modifyUser(this.userRepository, this.id);
}
}
@Inject
private OptimisticLockExceptionService service;
@Inject
private UserRepository userRepository;
private User u;
@Test(expected = ObjectOptimisticLockingFailureException.class)
public void thatOptimisticLockExceptionIsThrown() throws Exception {
this.u = new User("email", "p");
this.u = this.userRepository.save(this.u);
try {
Thread t1 = new ReadWithSleepRunnable(this.service, this.u.getId(), this.userRepository);
t1.start();
Thread.sleep(50);// To be sure the submitted thread starts
assertTrue(t1.isAlive());
Thread t2 = new ModifyRunnable(this.service, this.u.getId(), this.userRepository);
t2.start();
t2.join();
assertTrue(t1.isAlive());
t1.join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
The test service:
测试服务:
@Component
public class OptimisticLockExceptionService {
@Transactional
public User readWithSleep(UserRepository userRepo, int id) {
System.err.println("started read");
User op = userRepo.findOne(id);
Thread.currentThread();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("read end");
return op;
}
@Transactional
public User modifyUser(UserRepository userRepo, int id) {
System.err.println("started modify");
User op = userRepo.findOne(id);
op.setPassword("p2");
System.err.println("modify end");
return userRepo.save(op);
}
}
The repository:
存储库:
@Repository
public interface UserRepository extends CrudRepository<User, Integer> {
}
回答by Jens Schauder
Optimistic Locking with Spring Data JPA is implemented by the JPA implementation used.
使用 Spring Data JPA 的乐观锁定由所使用的 JPA 实现实现。
You are referring to P2 on page 93 of the JPA specs. The section starts with:
您指的是 JPA 规范第 93 页上的P2。该部分开始于:
If transaction T1 calls
lock(entity, LockModeType.OPTIMISTIC)
on a versioned object, the entity manager must ensure that neither of the following phenomena can occur:
如果事务 T1 调用
lock(entity, LockModeType.OPTIMISTIC)
版本化对象,实体管理器必须确保以下两种现象都不会发生:
But your test doesn't create such a scenario. The method lock
never gets called. Therefore no relevant locking happens. Especially just loading an entity doesn't call lock
on it.
但是您的测试不会创建这样的场景。该方法lock
永远不会被调用。因此不会发生相关的锁定。特别是只是加载一个实体不会调用lock
它。
Things change when one modifies an object (Page 93 second but last paragraph of the spec):
当一个人修改一个对象时,事情会发生变化(第 93 页,但规范的最后一段):
If a versioned object is otherwise updated or removed, then the implementation must ensure that the requirements of
LockModeType.OPTIMISTIC_FORCE_INCREMENT
are met, even if no explicit call toEntityManager.lock
was made.
如果版本化对象以其他方式更新或删除,则实现必须确保满足 的要求
LockModeType.OPTIMISTIC_FORCE_INCREMENT
,即使没有显式调用EntityManager.lock
。
Note: you are spawning two threads using the same repository, which in turn will make them use the same EntityManager
. I doubt if this is supported by EntityManager
and also I'm not sure if you are actually getting two transactions at all this way, but that is a question for another day.
注意:您正在使用同一个存储库生成两个线程,这反过来会使它们使用相同的EntityManager
. 我怀疑这是否得到支持,EntityManager
而且我不确定您是否真的以这种方式进行了两次交易,但这是另一天的问题。
回答by Veselin Davidov
The reason behind optimistic lock is to prevent updates on a table from previous state. For example:
乐观锁背后的原因是为了防止从以前的状态更新表。例如:
- You get user with id 1
- Another user updates and commit user with id 1 to a new state
- You update user 1 (which you loaded in step 1) and try to commit it to the database
- 您获得 ID 为 1 的用户
- 另一个用户更新并将 id 为 1 的用户提交到新状态
- 您更新用户 1(您在步骤 1 中加载)并尝试将其提交到数据库
In this case in step 3 you will override the changes the other guy did in step 2 and instead you need to throw an exception.
在这种情况下,在第 3 步中,您将覆盖其他人在第 2 步中所做的更改,而您需要抛出异常。
I believe spring does it with the @version property which corresponds to a version column in the database. The result is something like:
我相信 spring 使用 @version 属性来完成它,该属性对应于数据库中的版本列。结果是这样的:
update users set password="p2" where id=1 and version=1;
I think spring actually uses string for version but I am not sure. Probably a timestamp too but that's the general idea.
我认为 spring 实际上使用字符串作为版本,但我不确定。也可能是一个时间戳,但这是一般的想法。
You don't get an exception because only one of your threads is manipulating the data. You read it in thread 1 and current version is for example 1, then in thread 2 you read it - version is still 1. Then when you try to save it compares the version in the hibernate session to the one in the database and they match - everything is in order so it continues without exception. Try to make it updateWithSleep() and you should get the expected exception.
您不会收到异常,因为只有一个线程在操作数据。您在线程 1 中读取它,当前版本是例如 1,然后在线程 2 中读取它 - 版本仍然是 1。然后当您尝试保存它时,将休眠会话中的版本与数据库中的版本进行比较,它们匹配- 一切正常,所以它无一例外地继续。尝试使其 updateWithSleep() 并且您应该得到预期的异常。
回答by Har Krishan
You can set the optimistic-lock strategy like:
您可以设置乐观锁策略,如:
optimistic-lock (optional - defaults to version): determines the optimistic locking strategy.
乐观锁(可选 - 默认为版本):确定乐观锁策略。
optimistic locking strategies: version: check the version/timestamp columns, all: check all columns, dirty: check the changed columns none: do not use optimistic locking
乐观锁定策略:版本:检查版本/时间戳列,所有:检查所有列,脏:检查更改的列无:不使用乐观锁定
optimistic locking is completely handled by Hibernate.
乐观锁完全由 Hibernate 处理。
Optimistic locking Concept
乐观锁概念
Scenario to use: when concurrent updates are rare at the end of transaction check if updated by any other transaction
使用场景:当并发更新很少在事务结束时检查是否被任何其他事务更新
Concurrent updates can be handled using optimistic locking. Optimistic locking works by checking whether the data it is about to update has been changed by another transaction since it was read. e.g. you searched a record, after long time you are going to modify that record but in the meantime record has been updated by someone else. One common way to implement optimistic locking is to add a version column to each table, which is incremented by the application each time it changes a row. Each UPDATE statement's WHERE clause checks that the version number has not changed since it was read. If row has been updated or deleted by another transaction, application can roll back the transaction and start over. Optimistic locking derives its name from the fact it assumes that concurrent updates are rare, instead of preventing them, application detects and recovers from them. The Optimistic Lock pattern only detects changes when the user tries to save changes, it only works well when starting over is not a burden on user. When implementing use-cases where user would be extremely annoyed by having to discard several minutes' work, a much better option is to use the Pessimistic Lock.
可以使用乐观锁定处理并发更新。乐观锁的工作原理是检查它即将更新的数据自读取以来是否已被另一个事务更改。例如,您搜索了一条记录,经过很长时间您将要修改该记录,但同时记录已被其他人更新。实现乐观锁定的一种常见方法是向每个表添加一个版本列,应用程序每次更改一行时都会增加一个版本列。每个 UPDATE 语句的 WHERE 子句检查版本号自读取以来没有更改。如果行已被另一个事务更新或删除,应用程序可以回滚该事务并重新开始。乐观锁的名字来源于它假设并发更新很少见,而不是阻止它们,应用程序检测并从中恢复。Optimistic Lock 模式仅在用户尝试保存更改时检测更改,仅在重新开始对用户没有负担时才有效。在实现用户会因为不得不放弃几分钟的工作而非常恼火的用例时,更好的选择是使用悲观锁。
回答by Amr Alaa
The @Version annotation is used as a column in the database that should be added to each entity to perform the optimistic locking for this entity for example
@Version 注释用作数据库中的一列,应该添加到每个实体中,例如为该实体执行乐观锁定
@Entity
public class User {
@Version
@Column(nullable = false)
private Long version;
}
This will make sure that no user will be created with wrong version. Which means that you cannot update the user from multiple sources at the same time.
这将确保不会创建错误版本的用户。这意味着您不能同时从多个来源更新用户。