How to specify @Lock
timeout for query?
I am using Oracle 11g, I hope I can use something like 'select id from table where id = ?1 for update wait 5'
.
I defined method like this:
@Lock(LockModeType.PESSIMISTIC_WRITE)
Stock findById(String id);
It seems to lock forever.
When I set javax.persistence.lock.timeout=0
in LocalContainerEntityManagerFactoryBean.jpaProperties
, there is no effect.
javax. persistence. lock. timeout is defined as "milliseconds" in the JPA specification. But actually, it is set as "seconds" in SQL for Oracle Database.
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. Before the transaction wants to make an update, it checks the version property again.
PessimisticLockException will be thrown when we query for rows which are already locked. If the initial select query is successful, rows which meet the select query criteria are locked for the duration of a transaction. We can be sure that no other transaction will modify them.
Using the "test-oracle", "test-mysql" or "test-postgresql" profile for your integration tests will allow you to run all pessimistic locking handling tests against your production database. 🔔 This format is suitable for the local environment when you do changes to your pessimistic locking handling.
To lock entities pessimistically, set the lock mode to
PESSIMISTIC_READ
,PESSIMISTIC_WRITE
, orPESSIMISTIC_FORCE_INCREMENT
.If a pessimistic lock cannot be obtained, but the locking failure doesn’t result in a transaction rollback, a
LockTimeoutException
is thrown.Pessimistic Locking Timeouts
The length of time in milliseconds the persistence provider should wait to obtain a lock on the database tables may be specified using the javax.persistence.lock.timeout property. If the time it takes to obtain a lock exceeds the value of this property, a
LockTimeoutException
will be thrown, but the current transaction will not be marked for rollback. If this property is set to 0, the persistence provider should throw aLockTimeoutException
if it cannot immediately obtain a lock.If
javax.persistence.lock.timeout
is set in multiple places, the value will be determined in the following order:
- The argument to one of the
EntityManager
orQuery methods
.- The setting in the
@NamedQuery
annotation.- The argument to the
Persistence.createEntityManagerFactory
method.- The value in the
persistence.xml
deployment descriptor.
@Lock
is supported on CRUD methods as of version 1.6 of Spring Data JPA (in fact, there's already a milestone available). See this ticket for more details.
With that version you simply declare the following:
interface WidgetRepository extends Repository<Widget, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
Widget findOne(Long id);
}
This will cause the CRUD implementation part of the backing repository proxy to apply the configured LockModeType to the find(…)
call on the EntityManager
.
The Spring Data pessimistic @Lock
annotations only apply (as you pointed out) to queries. There are not annotations I know of which can affect an entire transaction. You can either create a findByOnePessimistic
method which calls findByOne
with a pessimistic lock or you can change findByOne
to always obtain a pessimistic lock.
If you wanted to implement your own solution you probably could. Under the hood the @Lock
annotation is processed by LockModePopulatingMethodIntercceptor
which does the following:
TransactionSynchronizationManager.bindResource(method, lockMode == null ? NULL : lockMode);
You could create some static lock manager which had a ThreadLocal<LockMode>
member variable and then have an aspect wrapped around every method in every repository which called bindResource with the lock mode set in the ThreadLocal. This would allow you to set the lock mode on a per-thread basis. You could then create your own @MethodLockMode
annotation which would wrap the method in an aspect which sets the thread-specific lock mode before running the method and clears it after running the method.
An entity object can be locked explicitly by the lock method:
em.lock(employee, LockModeType.PESSIMISTIC_WRITE);
The first argument is an entity object. The second argument is the requested lock mode.
A TransactionRequiredException
is thrown if there is no active transaction when lock is called because explicit locking requires an active transaction.
A LockTimeoutException
is thrown if the requested pessimistic lock cannot be granted:
PESSIMISTIC_READ
lock request fails if another user (which is
represented by another EntityManager instance) currently holds a
PESSIMISTIC_WRITE
lock on that database object.PESSIMISTIC_WRITE
lock request fails if another user currently
holds either a PESSIMISTIC_WRITE
lock or a PESSIMISTIC_READ
lock on
that database object.Query hints can be set in the following scopes (from global to local):
For the entire persistence unit - using a persistence.xml
property:
<properties>
<property name="javax.persistence.query.timeout" value="3000"/>
</properties>
For an EntityManagerFactory - using the createEntityManagerFacotory
method:
Map<String,Object> properties = new HashMap();
properties.put("javax.persistence.query.timeout", 4000);
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("pu", properties);
For an EntityManager - using the createEntityManager
method:
Map<String,Object> properties = new HashMap();
properties.put("javax.persistence.query.timeout", 5000);
EntityManager em = emf.createEntityManager(properties);
or using the setProperty method:
em.setProperty("javax.persistence.query.timeout", 6000);
For a named query
definition - using the hints
element:
@NamedQuery(name="Country.findAll", query="SELECT c FROM Country c",
hints={@QueryHint(name="javax.persistence.query.timeout", value="7000")})
For a specific query execution - using the setHint
method (before query execution):
query.setHint("javax.persistence.query.timeout", 8000);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With