Pessimistic concurrency control locks database data while a transaction works with it, preventing conflicting concurrent access. Use it when a successful read must remain safe for subsequent work, rather than discovering a conflict only when an optimistic transaction commits. section 11.2 section 11.2.3
In JPA, request a lock when loading data with a query or EntityManager.find(). PESSIMISTIC_READ guarantees repeatable reads. PESSIMISTIC_WRITE also requires serialized data access and prevents phantom reads. The lock lasts only for the database transaction; if it cannot be obtained, an exception is thrown. section 11.0 section 11.2.3
Lock items before calculating a total
Begin a transaction, query the items in a category with setLockMode(LockModeType.PESSIMISTIC_READ), and sum their prices before committing. A 5000-millisecond javax.persistence.lock.timeout hint requests a limit on the wait for a lock. A competing transaction that cannot obtain its lock fails rather than silently changing the calculation's inputs. section 11.2.3
Listing 11.6 Locking data pessimistically
Path: /Ch11/transactions/src/test/java/com/manning/javapersistence/ch11 /concurrency/Locking.java – pessimisticReadWrite() EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); BigDecimal totalPrice = BigDecimal.ZERO; for (Long categoryId : CATEGORIES) { List<Item> items = #1 em.createQuery("select i from Item i where i.category.id = :catId", Item.class) .setLockMode(LockModeType.PESSIMISTIC_READ) .setHint("javax.persistence.lock.timeout", 5000) .setParameter("catId", categoryId) .getResultList(); for (Item item : items) #2 totalPrice = totalPrice.add(item.getBuyNowPrice()); // . . . Another transaction tries to obtain a lock and fails } em.getTransaction().commit(); #3 em.close(); assertEquals(0, totalPrice.compareTo(new BigDecimal("108"))); #1 For each <kbd>Category</kbd>, query all <kbd>Item</kbd> instances in <kbd>PESSIMISTIC_READ</kbd> lock mode. Hibernate locks the rows in the database with the SQL query. If another transaction holds a conflicting lock, wait 5 seconds if possible. If the lock can’t be obtained, the query throws an exception. #2 If the query returns successfully, we know that we hold an exclusive lock on the data, and no other transaction can access it with an exclusive lock or modify it until this transaction commits. #3 The locks are released after committing, when the transaction completes.
The listing shows the transaction, locking query, timeout hint, and price calculation together. section 11.2.3
Choose and check the lock against the operation and database:
- Use PESSIMISTIC_READ for the stated repeatable-read guarantee; use PESSIMISTIC_WRITE when serialized access and protection against phantom reads are required. section 11.2.3
- Check the Hibernate dialect and database. PostgreSQL uses FOR SHARE or FOR UPDATE, MySQL uses LOCK IN SHARE MODE or FOR UPDATE, and H2 generates FOR UPDATE for both modes. section 11.2.3
- Keep the work within one transaction. A database lock cannot protect an operation that continues beyond that transaction. section 11.2.3
Pessimistic locking is a selective alternative to using serializable isolation everywhere or checking versions optimistically. The persistence context cache alone does not catch every concurrent change to query results, such as an item moving between categories. section 11.2.1 section 11.2.2
