Concurrency and Locking With JPA: Everything You Need to Know
Learn more about concurrency and locking with JPA.
Join the DZone community and get the full member experience.
Join For FreeImagine you have a system used by multiple users where each user is trying to modify the same entity concurrently. How do you ensure that the underlying data's integrity is preserved when accessed concurrently?
The persistence providers an offer locking strategy to manage concurrency. There are two types of locking: Optimistic locking and Pessimistic locking. Before we deep dive into the locking strategy, let's learn a little bit about ACID transactions.
ACID (Atomicity, Consistency, Isolation, Durability) transactions ensure that a database transaction is completed in a timely manner. The relational databases such as MySQL, Postgres, SQL Server, and Oracle are ACID compliant.
A database transaction can be broken down into multiple components. ACID-compliant databases ensure that a transaction is persisted and committed, only when all the components of a transaction are succeeded. If any one of the components fails, the transaction will be rolled back and no change will be made.
In case of multiple transactions, we need to define the locking strategy to make sure that underlying data's integrity is preserved.
Let's look at each locking strategy in this article.
Optimistic Locking
The persistence provider JPA provides javax.persistence.Version annotation to mark a property as a version attribute of an entity. This property can be a numeric (int, long, short) or java.sql.Timestamp field. Only one property with @Version annotation can exist per entity.
Also, the @Version attribute must be in the primary table for an entity mapped to multiple tables.
@Entity
public class User {
@Id
@Column (name = "ID", nullable = false)
@GeneratedValue (strategy = GenerationType.AUTO)
private long id;
@Version
private long version; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Order> orders = new ArrayList<>(); ..
}
Here, each transaction that reads data holds the value of the version property. Before any transaction that modifies the underlying data, it checks the value of version property again.
If the value has changed in the meantime an OptimisticLockException will be thrown and transaction will be rolled back. Otherwise, the transaction commits the update and increments the value of version property.
Optimistic Lock Modes
JPA provided two locking modes in case of Optimistic locking: OPTIMISTIC and OPTIMISTIC_FORCE_INCREMENT. OPTIMISTIC obtains the read lock for the entities with @Version property. OPTIMISTIC_FORCE_INCREMENT obtains the read lock for the entities with @Version property and increments the value of the property.
@Service
public class UserService {
@PersistenceContext
private EntityManager entityManager;
public void updateUser(final String id) {
User user =
entityManager.find(User.class, id);
entityManager.lock(user,
LockModeType.OPTIMISTIC); ..
}
}
public void updateUser(final String id) {
User user =
entityManager.find(User.class, id);
entityManager.lock(user,
LockModeType.OPTIMISTIC_FORCE_INCREMENT); ..
}
Pessimistic Locking
In case of pessimistic locking, JPA creates a transaction that obtains a lock on the data until the transaction is completed. This prevents other transactions from making any updates to the entity until the lock is released.
Pessimistic locking can be very useful when the data is frequently accessed and modified by multiple transactions.
Keep in mind that using pessimistic locking may result in decreased application performance, if the entities are not susceptible to frequent modifications.
Pessimistic Lock Modes
JPA provides three Pessimistic locking modes: PESSIMISTIC_READ, PESSIMISTIC_FORCE_INCREMENT and PESSIMISTIC_WRITE.
PESSIMISTIC_READ obtains a long-term read lock on the data to prevent the data from being updated or deleted. Other transactions may read the data during the lock, but will not be able to modify or delete the data.
PESSIMISTIC_FORCE_INCREMENT obtains a long-term read lock on the data to prevent the data from being updated or deleted. Also, increments the value of@Version property.
PESSIMISTIC_WRITE obtains a long-term read lock on the data to prevent the data from being read, updated or deleted.
User user =
entityManager.find(User.class,id);
entityManager.lock(user,
LockModeType.PESSIMISTIC_WRITE);
user.getOrders().forEach(order -> {
orderRepository.delete(order);
}); ..
If a pessimistic lock cannot be obtained, it results in a transaction rollback and PessimisticLockException will be thrown. But, if the locking failure doesn't result in a transaction rollback, a LockTimeoutException will be thrown.
Conclusion
Depending on your application needs, choose the right locking strategy and apply locking strategy only if necessary. Feel free to let me know if you have any suggestions or comments below.
Published at DZone with permission of Swathi Prasad, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments