1. Overview
1.概述
When it comes to enterprise applications, properly managing concurrent access to a database is crucial. This means we should be able to handle multiple transactions in an effective and, most importantly, error-proof way.
当涉及到企业应用程序时,正确管理对数据库的并发访问是至关重要的。这意味着我们应该能够有效地处理多个事务,而且最重要的是防错的方式。
What’s more, we need to ensure that data stays consistent between concurrent reads and updates.
更重要的是,我们需要确保数据在并发的读取和更新之间保持一致。
To achieve that, we can use an optimistic locking mechanism provided by Java Persistence API. This way, multiple updates made on the same data at the same time do not interfere with each other.
为了实现这一点,我们可以使用Java Persistence API提供的乐观锁机制。这样,在同一时间对同一数据进行的多个更新就不会相互干扰。
2. Understanding Optimistic Locking
2.了解乐观的锁定
In order to use optimistic locking, we need to have an entity including a property with @Version annotation. While using it, each transaction that reads data holds the value of the version property.
为了使用乐观锁,我们需要有一个实体,包括一个带有@Version注解的属性。在使用它时,每个读取数据的事务都持有版本属性的值。
Before the transaction wants to make an update, it checks the version property again.
在事务想要进行更新之前,它再次检查版本属性。
If the value has changed in the meantime, an OptimisticLockException is thrown. Otherwise, the transaction commits the update and increments a value version property.
如果在此期间值发生了变化,就会抛出一个OptimisticLockException。否则,事务会提交更新,并增加一个值的版本属性。
3. Pessimistic Locking vs Optimistic Locking
3.悲观的锁定与乐观的锁定
In contrast to optimistic locking, JPA gives us pessimistic locking. It’s another mechanism for handling concurrent access for data.
与乐观的锁相比,JPA给我们提供了悲观的锁。这是另一种处理数据并发访问的机制。
We cover pessimistic locking in one of our previous articles — Pessimistic Locking in JPA. Let’s find out the difference between them and how we can benefit from each type of locking.
我们在之前的一篇文章–JPA中的悲观锁中介绍了悲观锁。让我们来看看它们之间的区别,以及我们如何从每种类型的锁中获益。
As we’ve said before, optimistic locking is based on detecting changes on entities by checking their version attribute. If any concurrent update takes place, OptmisticLockException occurs. After that, we can retry updating the data.
正如我们之前所说,优化锁定是基于通过检查实体的版本属性来检测其变化。如果有任何并发的更新发生,OptmisticLockException会发生。之后,我们可以重新尝试更新数据。
We can imagine that this mechanism is suitable for applications that do many more reads than updates or deletes. It’s also useful in situations where entities must be detached for some time and locks cannot be held.
我们可以想象,这种机制适用于那些读取次数多于更新或删除次数的应用。在实体必须被分离一段时间而锁不能被保持的情况下,它也很有用。
On the contrary, the pessimistic locking mechanism involves locking entities on the database level.
相反,悲观的锁定机制涉及在数据库层面上锁定实体。。
Each transaction can acquire a lock on data. As long as it holds the lock, no transaction can read, delete or make any updates on the locked data.
每个事务都可以获得一个数据锁。只要它持有锁,任何事务都不能读取、删除或对锁定的数据进行任何更新。
We can presume that using pessimistic locking may result in deadlocks. However, it ensures greater integrity of data than optimistic locking.
我们可以推测,使用悲观的锁可能会导致死锁。然而,它比乐观锁更能保证数据的完整性。
4. Version Attributes
4.版本属性
Version attributes are properties with @Version annotation. They are necessary for enabling optimistic locking.
版本属性是带有@Version注解的属性。它们对于启用乐观锁定是必要的。。
Let’s see a sample entity class:
让我们看看一个实体类的例子。
@Entity
public class Student {
@Id
private Long id;
private String name;
private String lastName;
@Version
private Integer version;
// getters and setters
}
There are several rules that we should follow while declaring version attributes:
在声明版本属性时,有几条规则是我们应该遵循的。
- Each entity class must have only one version attribute.
- It must be placed in the primary table for an entity mapped to several tables.
- Type of a version attribute must be one of the following: int, Integer, long, Long, short, Short, java.sql.Timestamp.
We should know that we can retrieve a value of the version attribute via entity, but we shouldn’t update or increment it. Only the persistence provider can do that, so data stays consistent.
我们应该知道,我们可以通过实体检索版本属性的值,但我们不应该更新或增加它。只有持久化提供者可以这样做,所以数据会保持一致。
Notice that persistence providers are able to support optimistic locking for entities that don’t have version attributes. Yet, it’s a good idea to always include version attributes when working with optimistic locking.
请注意,持久化提供者能够支持对没有版本属性的实体进行乐观锁定。然而,在使用乐观锁的时候,最好总是包含版本属性。
If we try to lock an entity that does not contain such attributes and the persistence provider does not support it, it will result in a PersistenceException.
如果我们试图锁定一个不包含此类属性的实体,并且持久化提供者不支持它,那么将导致一个PersistenceException。
5. Lock Modes
5.锁定模式
JPA provides us with two different optimistic lock modes (and two aliases):
JPA为我们提供了两种不同的乐观锁模式(和两个别名)。
- OPTIMISTIC obtains an optimistic read lock for all entities containing a version attribute.
- OPTIMISTIC_FORCE_INCREMENT obtains an optimistic lock the same as OPTIMISTIC and additionally increments the version attribute value.
- READ is a synonym for OPTIMISTIC.
- WRITE is a synonym for OPTIMISTIC_FORCE_INCREMENT.
We can find all types listed above in the LockModeType class.
我们可以在LockModeType类中找到上面列出的所有类型。
5.1. OPTIMISTIC (READ)
5.1.OPTIMISTIC (READ)
As we already know, OPTIMISTIC and READ lock modes are synonyms. However, JPA specification recommends us to use OPTIMISTIC in new applications.
我们已经知道,OPTIMISTIC和READ锁模式是同义词。然而,JPA规范建议我们在新的应用程序中使用OPTIMISTIC。
Whenever we request the OPTIMISTIC lock mode, a persistence provider will prevent our data from dirty reads as well as non-repeatable reads.
每当我们请求OPTIMISTIC锁模式时,持久化提供者将防止我们的数据被脏读以及不可重复的读。
Put simply, it should ensure any transaction fails to commit any modification on data that another transaction
简单地说,它应该确保任何事务都不能提交对另一个事务的数据的任何修改。
- has updated or deleted but not committed
- has updated or deleted successfully in the meantime
5.2. OPTIMISTIC_INCREMENT (WRITE)
5.2. OPTIMISTIC_INCREMENT(WRITE)
The same as before, OPTIMISTIC_INCREMENT and WRITE are synonyms, but the former is preferable.
和以前一样,OPTIMISTIC_INCREMENT和WRITE是同义词,但前者更好。
OPTIMISTIC_INCREMENT must meet the same conditions as OPTIMISTIC lock mode. Additionally, it increments the value of a version attribute. However, it’s not specified whether it should be done immediately or may be put off until commit or flush.
OPTIMISTIC_INCREMENT必须满足与OPTIMISTIC锁模式相同的条件。此外,它还会增加一个版本属性的值。然而,并没有规定它是否应该立即进行,还是可以推迟到提交或刷新时进行。
It’s worth knowing that a persistence provider is allowed to provide OPTIMISTIC_INCREMENT functionality when OPTIMISTIC lock mode is requested.
值得一提的是,当请求OPTIMISTIC_INCREMENT锁模式时,允许持久化提供者提供OPTIMISTIC功能。
6. Using Optimistic Locking
6.使用乐观的锁定
We should remember that for versioned entities optimistic locking is available by default. But there are several ways of requesting it explicitly.
我们应该记住,对于有版本的实体来说,乐观的锁定是默认可用的。但是有几种方法可以明确地请求它。
6.1. Find
6.1. 查找
To request optimistic locking, we can pass the proper LockModeType as an argument to find method of EntityManager:
为了请求乐观的锁定,我们可以将适当的LockModeType作为参数传递给EntityManager的查找方法。
entityManager.find(Student.class, studentId, LockModeType.OPTIMISTIC);
6.2. Query
6.2. 查询
Another way to enable locking is using the setLockMode method of Query object:
另一种启用锁定的方法是使用Query对象的setLockMode方法。
Query query = entityManager.createQuery("from Student where id = :id");
query.setParameter("id", studentId);
query.setLockMode(LockModeType.OPTIMISTIC_INCREMENT);
query.getResultList()
6.3. Explicit Locking
6.3.显式锁定
We can set a lock by calling EntityManager’s lock method:
我们可以通过调用EntityManager的lock方法设置一个锁。
Student student = entityManager.find(Student.class, id);
entityManager.lock(student, LockModeType.OPTIMISTIC);
6.4. Refresh
6.4 刷新
We can call the refresh method the same way as the previous method:
我们可以用与前一个方法相同的方法调用refresh方法。
Student student = entityManager.find(Student.class, id);
entityManager.refresh(student, LockModeType.READ);
6.5. NamedQuery
6.5 NamedQuery
The last option is to use @NamedQuery with the lockMode property:
最后一个选项是使用@NamedQuery与lockMode属性。
@NamedQuery(name="optimisticLock",
query="SELECT s FROM Student s WHERE s.id LIKE :id",
lockMode = WRITE)
7. OptimisticLockException
7.OptimisticLockException
When the persistence provider discovers optimistic locking conflicts on entities, it throws OptimisticLockException. We should be aware that due to the exception the active transaction is always marked for rollback.
当持久化提供者发现实体上有乐观的锁定冲突时,它会抛出OptimisticLockException。我们应该注意到,由于该异常,活动事务总是被标记为回滚。
It’s good to know how we can react to OptimisticLockException. Conveniently, this exception contains a reference to the conflicting entity. However, it’s not mandatory for the persistence provider to supply it in every situation. There is no guarantee that the object will be available.
知道我们如何对OptimisticLockException做出反应是件好事。方便的是,这个异常包含一个对冲突实体的引用。然而,持久化提供者并不是在每种情况下都必须提供它。不能保证该对象是可用的。
There is a recommended way of handling the described exception, though. We should retrieve the entity again by reloading or refreshing, preferably in a new transaction. After that, we can try to update it once more.
不过,有一种推荐的方式来处理所述的异常。我们应该通过重新加载或刷新来再次检索该实体,最好是在一个新的事务中。之后,我们可以尝试再次更新它。
8. Conclusion
8.结语
In this article, we got familiar with a tool that can help us orchestrate concurrent transactions.
在这篇文章中,我们熟悉了一个可以帮助我们协调并发事务的工具。
Optimistic locking uses version attributes included in entities to control concurrent modifications on them.
乐观的锁定使用实体中包含的版本属性来控制对其进行的并发修改。
Therefore, it ensures that any updates or deletes won’t be overwritten or lost silently. Opposite to pessimistic locking, it doesn’t lock entities on the database level, and consequently, it isn’t vulnerable to DB deadlocks.
因此,它确保任何更新或删除都不会被覆盖或无声地丢失。与悲观锁相反,它不在数据库层面上锁定实体,因此,它不容易出现数据库死锁。
We’ve learned that optimistic locking is enabled for versioned entities by default. However, there are several ways of requesting it explicitly by using various lock mode types.
我们已经了解到,乐观的锁在默认情况下是为版本实体启用的。然而,有几种方法可以通过使用各种锁模式类型来明确请求它。
We should also remember that each time there are conflicting updates on entities, we should expect an OptimisticLockException.
我们还应该记住,每次在实体上有冲突的更新时,我们应该期待一个OptimisticLockException。
Lastly, the source code of this article is available over on GitHub.
最后,本文的源代码可在GitHub上获得,。