Controlling how and when objects are locked is an important part of maximizing the performance of your application under load. This section describes Kodo's APIs for explicit locking, as well as its rules for implicit locking.
You can control Kodo's default transactional read and write lock levels
configuration properties. Each property accepts a
write, or a number corresponding to a lock
level defined by the lock
manager in use. These properties apply only to non-optimistic
transactions; during optimistic transactions, Kodo never locks objects
You can control the default amount of time Kodo will wait when trying
to obtain locks through the
kodo.LockTimeout configuration property. Set this
property to the number of milliseconds you are willing to wait for
a lock before Kodo will throw an exception, or to -1 for no limit. It
defaults to -1.
Example 9.4. Setting Default Lock Levels
JPA XML format:
<property name="kodo.ReadLockLevel" value="none"/> <property name="kodo.WriteLockLevel" value="write"/> <property name="kodo.LockTimeout" value="30000"/>
JDO properties format:
kodo.ReadLockLevel: none kodo.WriteLockLevel: write kodo.LockTimeout: 30000
At runtime, you can override the default lock levels in JPA
kodo.persistence.FetchPlan, or in JDO
interfaces are described above.
At the beginning of each datastore transaction, Kodo initializes the
PersistenceManager's fetch plan with the default
lock levels and timeouts described in the previous section. By
changing the fetch plan's locking properties, you can control
how objects loaded at different points in the transaction are locked.
You can also use the fetch plan of an individual
Extent to apply
your locking changes only to objects loaded through that
LockModeType represents no locking:
public LockModeType getReadLockMode (); public FetchPlan setReadLockMode (LockModeType mode); public LockModeType getWriteLockMode (); public FetchPlan setWriteLockMode (LockModeType mode); long getLockTimeout (); FetchPlan setLockTimeout (long timeout);
public int getReadLockLevel (); public KodoFetchPlan setReadLockLevel (int level); public int getWriteLockLevel (); public KodoFetchPlan setWriteLockLevel (int level); long getLockTimeout (); KodoFetchPlan setLockTimeout (long timeout);
Controlling locking through these runtime APIs works even during
optimistic transactions. At the end of the transaction, Kodo resets
the fetch plan's lock levels to
You cannot lock objects outside of a transaction.
Example 9.5. Setting Runtime Lock Levels
import kodo.persistence.*; ... EntityManager em = ...; em.getTransaction ().begin (); // load stock we know we're going to update at write lock mode Query q = em.createQuery ("select s from Stock s where symbol = :s"); q.setParameter ("s", symbol); KodoQuery kq = KodoPersistence.cast (q); FetchPlan fetch = kq.getFetchPlan (); fetch.setReadLockMode (LockModeType.WRITE); fetch.setLockTimeout (3000); // 3 seconds Stock stock = (Stock) q.getSingleResult (); // load an object we don't need locked at none lock mode fetch = (KodoPersistence.cast (em)).getFetchPlan (); fetch.setReadLockMode (null); Market market = em.find (Market.class, marketId); stock.setPrice (market.calculatePrice (stock)); em.getTransaction ().commit ();
import kodo.jdo.*; ... PersistenceManager pm = ...; pm.currentTransaction ().begin (); // load stock we know we're going to update at write lock level Query q = pm.newQuery (Stock.class, "symbol == :s"); q.setUnique (true); KodoFetchPlan fetch = (KodoFetchPlan) q.getFetchPlan (); fetch.setReadLockLevel (KodoFetchPlan.LOCK_WRITE); fetch.setLockTimeout (3000); // 3 seconds Stock stock = (Stock) q.execute (symbol); // load an object we don't need locked at none lock level fetch = (KodoFetchPlan) pm.getFetchPlan (); fetch.setReadLockLevel (KodoFetchPlan.LOCK_NONE); Market market = (Market) pm.getObjectById (marketId); stock.setPrice (market.calculatePrice (stock)); pm.currentTransaction ().commit ();
In addition to allowing you to control implicit locking levels, Kodo provides explicit APIs to lock objects and to retrieve their current lock level.
public LockModeType KodoEntityManager.getLockMode (Object pc);
public static int KodoJDOHelper.getLockLevel (Object pc);
Returns the level at which the given object is currently locked.
public void lock (Object pc); public void lock (Object pc, LockModeType mode, long timeout); public void lockAll (Object... pcs); public void lockAll (Object... pcs, LockModeType mode, long timeout); public void lockAll (Collection pcs); public void lockAll (Collection pcs, LockModeType mode, long timeout);
The Kodo JDO
KodoPersistenceManager exposes the
following methods to lock objects explicitly:
public void lockPersistent (Object pc); public void lockPersistent (Object pc, int level, long timeout); public void lockPersistentAll (Object pcs); public void lockPersistentAll (Object pcs, int level, long timeout); public void lockPersistentAll (Collection pcs); public void lockPersistentAll (Collection pcs, int level, long timeout);
Methods that do not take a lock level or timeout parameter default to the current fetch plan. The example below demonstrates these methods in action.
Example 9.6. Locking APIs
import kodo.persistence.*; // retrieve the lock level of an object KodoEntityManager kem = KodoPersistence.cast (em); Stock stock = ...; LockModeType level = kem.getLockMode (stock); if (level == KodoModeType.WRITE) ... ... kem.setOptimistic (true); kem.getTransaction ().begin (); // override default of not locking during an opt trans to lock stock object kem.lock (stock, LockModeType.WRITE, 1000); stock.setPrice (market.calculatePrice (stock)); kem.getTransaction ().commit ();
import kodo.jdo.*; // retrieve the lock level of an object Stock stock = ...; int level = KodoJDOHelper.getLockLevel (stock); if (level == KodoJDOHelper.LOCK_WRITE) ... ... PersistenceManager pm = ...; pm.currentTransaction ().setOptimistic (true); pm.currentTransaction ().begin (); // override default of not locking during an opt trans to lock stock object KodoPersistenceManager kpm = KodoJDOHelper.cast (pm); kpm.lockPersistent (stock, KodoPersistenceManager.LOCK_WRITE, -1); stock.setPrice (market.calculatePrice (stock)); pm.currentTransaction ().commit ();
Kodo delegates the actual work of locking objects to the system's
kodo.kernel.LockManager. This plugin is controlled
configuration property. You can write your own lock
manager, or use one of the bundled options:
pessimistic: This is an alias for the
, which uses SELECT FOR UPDATE statements (or the
database's equivalent) to lock the database rows corresponding
to locked objects. This lock manager does not distinguish
between read locks and write locks; all locks are write locks.
pessimistic LockManager can be
configued to additionally perform the version checking
and incrementing behavior of the
lock manager described below by setting its
JPA XML format:
<property name="kodo.LockManager" value="pessimistic(VersionCheckOnReadLock=true,VersionUpdateOnWriteLock=true)"/>
JDO properties format:
This is the default
setting in JDO.
none: An alias for the
which does not perform any locking at all.
sjvm: An alias for the
. This lock manager uses in-memory mutexes
to obtain exclusive locks on object ids. It does not perform
any database-level locking. Also, it does not distinguish
between read and write locks; all locks are write locks.
version: An alias for the
. This lock manager does not perform
any exclusive locking, but instead ensures read consistency
by verifying that the version of all read-locked instances
is unchanged at the end of the transaction. Furthermore, a
write lock will force an increment to the version at the
end of the transaction, even if the object is not
otherwise modified. This ensures read consistency with
This is the default
setting in JPA.
In order for the
Advanced persistence concepts like lazy-loading and object uniquing create several locking corner-cases. The rules below outline Kodo's implicit locking behavior in these cases.
When an object's state is first read within a transaction, the object is locked at the fetch plan's current read lock level. Future reads of additional lazy state for the object will use the same read lock level, even if the fetch plan's level has changed.
When an object's state is first modified within a transaction, the object is locked at the write lock level in effect when the object was first read, even if the fetch plan's level has changed. If the object was not read previously, the current write lock level is used.
When objects are accessed through a persistent relation field, the related objects are loaded with the fetch plan's current lock levels, not the lock levels of the object owning the field.
Whenever an object is accessed within a transaction, the object is re-locked at the current read lock level. The current read and write lock levels become those that the object "remembers" according to rules one and two above.
If you lock an object explicitly through the APIs demonstrated above, it is re-locked at the specified level. This level also becomes both the read and write level that the object "remembers" according to rules one and two above.
When an object is already locked at a given lock level, re-locking at a lower level has no effect. Locks cannot be downgraded during a transaction.
Due to performance concerns and database limitations, locking cannot be perfect. You should be aware of the issues outlined in this section, as they may affect your application.
Typically, during optimistic transactions Kodo does not
start an actual database transaction until you flush or the
optimistic transaction commits. This allows for very long-lived
transactions without consuming database resources.
When using the default lock manager, however, Kodo must begin a
database transaction whenever you decide to lock an object
during an optimistic transaction. This is because the
default lock manager uses database locks, and databases cannot
lock rows without a transaction in progress. Kodo will log
an INFO message to the
channel when it begins a datastore transaction just to lock
In order to maintain reasonable performance levels when loading object state, Kodo can only guarantee that an object is locked at the proper lock level after the state has been retrieved from the database. This means that it is technically possible for another transaction to "sneak in" and modify the database record after Kodo retrieves the state, but before it locks the object. The only way to positively guarantee that the object is locked and has the most recent state to refresh the object after locking it.
When using the default lock manager, the case above can only
occur when Kodo cannot issue the state-loading SELECT as a
locking statement due to database limitations. For example,
some databases cannot lock SELECTs that use joins.
The default lock manager will log an INFO message to the
kodo.Runtime logging channel whenever it
cannot lock the initial SELECT due to database limitations.
By paying attention to these log messages, you can see where
you might consider using an object refresh to guarantee that
you have the most recent state, or where you might rethink the
way you load the state in question to circumvent the database
limitations that prevent Kodo from issuing a locking SELECT in
the first place.