Kodo supports both optimistic and datastore locking strategies, but optimistic locking is the preferred approach in most applications. Typically, optimistic locking is performed at the object level of granularity. That is, changes to any part of the same object in concurrent transactions will result in an optimistic locking exception being thrown by the transaction that commits last. In many applications, this is acceptable. However, if your application has a high likelihood of concurrent writes to different parts of the same object, then it may be advantageous to use a finer-grained optimistic lock. Additionally, certain parts of an object model may be best modeled without any locking at all, or with a last-commit-wins strategy. It is for these types of situations that Kodo offers customizable optimistic lock groups, which allow you to achieve sub-object-level locking granularity.
For example, an Employee
class may have some fields
configurable by the employee the object represents (
firstName
, lastName
,
phoneNumber
), some that are only modifiable by that employee's
manager (salary
, title
), and some in
which concurrent updates are acceptable (a list of
projects
). In such a model, you can greatly
improve the success of concurrent updates in optimistic transactions
by putting firstName
, lastName
, and
phoneNumber
into one lock group,
salary
and title
into another, and
excluding the projects
field from optimistic lock
checks altogether.
You specify a field's lock group in JPA metadata with the
kodo.persistence.LockGroup
annotation.
You specify a field's lock group in JDO
metadata with the lock-group
metadata extension.
See Section 6.4.2.6, “Lock Group” for details on lock group metadata.
Example 5.22. Lock Group Metadata
JPA:
import kodo.persistence.*; @Entity public class Employee { // default lock group private String firstName; private String lastName; private String phoneNumber; // named lock group @LockGroup("corporate") private float salary; @LockGroup("corporate") private String title; // no lock group; allow concurrent modifications @LockGroup(LockGroup.NONE) private Set<Project> projects; ... }
JDO:
public class Employee { private String firstName; private String lastName; private String phoneNumber; private float salary; private String title; private Set projects; ... } <?xml version="1.0"?> <jdo> <package name=""> <class name="Employee"> <!-- named lock group --> <field name="salary"> <extension vendor-name="kodo" key="lock-group" value="corporate"/> </field> <field name="title"> <extension vendor-name="kodo" key="lock-group" value="corporate"/> </field> <!-- no lock group; allow concurrent modifications --> <field name="projects"> <collection element-type="Project"/> <extension vendor-name="kodo" key="lock-group" value="none"/> </field> </class> </package> </jdo>
Currently, lock groups are only supported when using number and
timestamp version strategies. They are not supported in the
state-comparison strategy, though you can still exclude
fields from participating in optimistic versioning under this strategy
by setting the their lock group to none
.
Due to mapping restrictions, subclasses cannot simply declare
additional lock groups implicitly, as is done in the example shown
above. Instead, the least-derived mapped type in the persistent
hierarchy must list all lock groups that its children can use via the
kodo.persistence.LockGroups
annotation
in JPA metadata, or with JDO's
class-level lock-groups
metadata extension.
For example, if the Employee
class in the last example extended Person
, the
metadata would have looked like so:
Example 5.23. Lock Group Metadata
JPA:
import kodo.persistence.*; @Entity @LockGroups({"corporate"}) public class Person { // default lock group private String firstName; private String lastName; private String phoneNumber; ... } @Entity public class Employee extends Person { // named lock group @LockGroup("corporate") private float salary; @LockGroup("corporate") private String title; // no lock group; allow concurrent modifications @LockGroup(LockGroup.NONE) private Set<Project> projects; ... }
JDO:
public class Person { private String firstName; private String lastName; private String phoneNumber; ... } public class Employee extends Person { // these fields can only be set by the employee's manager private float salary; private String title; // this field might be updated concurrently by the employee, // other team members, or the employee's manager private Set projects; ... } <?xml version="1.0"?> <jdo> <package name=""> <class name="Person"> <!-- here we list the lock groups that will be used by Employee --> <extension vendor-name="kodo" key="lock-groups" value="corporate"/> </class> <class name="Employee"> <!-- named lock group --> <field name="salary"> <extension vendor-name="kodo" key="lock-group" value="corporate"/> </field> <field name="title"> <extension vendor-name="kodo" key="lock-group" value="corporate"/> </field> <!-- no lock group; allow concurrent modifications --> <field name="projects"> <collection element-type="Project"/> <extension vendor-name="kodo" key="lock-group" value="none"/> </field> </class> </package> </jdo>
The exceptions to this rule are the none
and
default
built-in lock groups. They can be used
at any point in the inheritance hierarchy without pre-declaration.
Additionally, the lock groups listing can contain
lock groups that would otherwise be implicitly defined in the
least-derived type metadata.
When using custom lock groups with a relational database, Kodo will need a version column for each of the groups, instead of just one version column. This means that you must use surrogate versioning; you cannot use a version field. Kodo also currently requires that all the version columns for a given object be in the same table. Finally, it is only possible to use a single version strategy for a given object. That is, you cannot have one version number column and another timestamp version column.
Use the
kodo.persistence.jdbc.LockGroupVersionColumn(s)
annotation to specify the version column for each lock group in JPA
mapping.
In your JDO mappings, use the lock-group
column
extension to specify the version column for each lock group.
Example 5.24. Mapping Lock Groups
JPA:
import kodo.persistence.jdbc.*; @Entity @Table(name="EMP") @LockGroupVersionColumns({ @LockGroupVersionColumn(name="VERS_CORP" lockGroup="corporate"), @LockGroupVersionColumn(name="VERS") }) public class Employee { ... }
JDO:
<class name="Employee" table="EMP"> <version strategy="version-number"> <column name="VERS_CORP"> <extension vendor-name="kodo" key="lock-group" value="corporate"/> </column> <!-- column for default group doesn't need an extension --> <column name="VERS"/> </version> ... </class>