Locking Mechanisms

JBossCMP works in conjunction with the Entity locking mechanisms in JBoss to control access to CMP Entities and avoid potentially inconsistent modifications to data. Unlike BMP, the application code does not need to participate in the locking strategies.

The EJB specification mandates a Serialized isolation model, which provides the greatest protection against inadvertant data changes but as the cost of a significant impact on performance. This is at odds with database systems which typically operate at a Read Committed isolation level, offering less protection but greatly increasing concurrency.

The specification, however, does not state how such isolation is obtained, and explicitly allows the Container to support both pessimistic and optimistic locking strategies.

With a pessimistic strategy, serialization is obtained by by taking an exclusive lock on any data that is used by a transaction; if one transaction needs data that has been used in another transaction, it must wait until the other transaction is over. Because data is locked on read, this can very easily reduce concurrency to a single flow. It also greatly increases the chance of a deadlock.

With an optimistic strategy, serialization is obtained by versioning the data as it is used and then comparing the version read with the current version as data is written; if the versions are the same, no conflicts occurred and the transaction can commit, if the versions differ then serialization was not achieved and the entire transaction is rolled back. Because this does not perform any locking, concurrency can be very high and deadlocks cannot occur; however, a transaction can fail at any time and the application must be prepared to handle this.

Pessimistic Locking with a Single Node

In a single node configuration, JBoss CMP by default operates with a hybrid isolation model.

Inside JBoss, a locking mechanism is used to ensure access to all Entities is serialized. However, JBossCMP interacts with the database using its default isolation level, which is typically read committed. If all access to the database is through entities in the JBoss instance, then serialized isolation will be maintained. However, other forms of access to the database such as direct JDBC calls or external applications may violate this.

To ensure serialized access at the database level, JBossCMP can be configured to use SQL statements that lock all data on read by using a row-locking element in jbosscmp-jdbc.xml:

    <entity>
<ejb-name>Order</ejb-name>

<!-- enable row locking via SELECT ... FOR UPDATE -->
<row-locking>true</row-locking>
</entity>

This will cause JBossCMP to add a FOR UPDATE clause to every SELECT statement executed resulting in an exclusive lock in the database. If the same data is being accessed in BMP code, then it must also use SELECT ... FOR UPDATE to ensure it obtains the locks as well.

Pessimistic Locking in a Cluster

In a clustered configuration, each node operates with the hybrid isolation model described above; however, access at the EJB level is not serialized across the cluster, making the global isolation level read committed.

To ensure global isolation, serialization must be delegated to the database using row locking. This can be done for every entity in an ejb-jar by setting it in the defaults section of jbosscmp-jdbc.xml:

<jbosscmp-jdbc>
<defaults>
<row-locking>true</row-locking>
</defaults>
</jbosscmp-jdbc>

Optimistic Locking

To avoid the concurrency penalty of pessimistic locking, JBossCMP can be configured to use an optimistic locking strategy. In this mode, locking is avoided at the Entity level by allowing each transaction to access a separate copy of the entity's data. Whenever JBossCMP updates the database, the version that was loaded is compared to the current version in the database and if they are different then an EJBException is thrown which causes the transaction to be rolledback.

To enable optimistic locking at the EJB level, the container configuration must be changed to allow separate instances of the Entity to be loaded for each transaction. The "Instance Per Transaction" configuration does not normally perform any locking and so its locking policy must also be overridden. This is configured in jboss.xml:

<jboss>
   <enterprise-beans>
<entity>
<ejb-name>Order</ejb-name>
<configuration-name>Optimistic CMP EntityBean</configuration-name>
</entity>
</enterprise-beans>

<container-configurations>
<container-configuration extends="Instance Per Transaction CMP 2.x EntityBean">
<container-name>Optimistic CMP EntityBean</container-name>
<locking-policy>org.jboss.ejb.plugins.lock.JDBCOptimisticLock</locking-policy>
</container-configuration>
</container-configurations>
</jboss>

JBossCMP supports several mechanisms for detecting conflicting changes. These are described in the following sections.

Field Group Locking Strategy

The Field Group locking strategy uses the values of the fields in a specific load group to detect if a concurrent modification has occurred. When the entity is being stored, the values of all these fields are added to the where clause for the update. If no row is found, then an EJBException is thrown to cause the transaction to rollback.

This is configued in jbosscmp-jdbc.xml:

    <entity>
<ejb-name>Order</ejb-name>
<load-groups>
<load-group>
<load-group-name>Billing</load-group-name>
<field-name>orderDate</field-name>
<field-name>lastUpdated</field-name>
</load-group>
<load-group>
<load-group-name>Locking</load-group-name>
<field-name>lastUpdated</field-name>
</load-group>
</load-groups>
<eager-load-group>Billing</eager-load-group>
<optimistic-locking>
<!-- the name of the load group whose fields should be compared -->
<group-name>Locking</group-name>
</optimistic-locking>
</entity>

This defines a load group containing the lastUpdated cmp-field which the application uses to store the time the order was last modified. When the Order is loaded, this field will be eager loaded resulting in the SQL:

  SELECT ORDER_DATE, LAST_UPDATED
FROM ORDER_DATA
WHERE ORDER_ID = ?

When the Order is being stored, the values that were originally loaded are used to determine if the row has changed

  UPDATE ORDER_DATA SET ORDER_DATE = ?, LAST_UPDATED = ?
WHERE ORDER_ID = ?
AND LAST_UPDATED = ?

The new values are used in the SET clause, the old values are used in the WHERE clause.

This strategy relies on the application modifying a specific field or fields for every update that is performed; if no field in the load group is modified then this will not detect the change which will lead to a silent concurrency violation.

Modified Field Locking Strategy

The Modified Field strategy is similar to the Field Group strategy except that is uses every field that was modified by the application to determine if the data has been changed in the database. This does not require any load group to be configured and ensures that every field modification is caught. However, this may fail due to rounding errors for approximate numeric values or if the modified column is of a type that the database cannot compare; for example, some databases do not allow LOB columns to be compared.

This is configured in jbossxmp-jdbc.xml:

    <entity>
<ejb-name>Order</ejb-name>
<optimistic-locking>
<modified-strategy/>
</optimistic-locking>
</entity>

This strategy does not change the SQL used to load any values. The update statement is changed to include all modified values; for example, if the orderDate and lastUpdated fields where changed, the SQL would be:

  UPDATE ORDER_DATA SET ORDER_DATE = ?, LAST_UPDATED = ?
WHERE ORDER_ID = ?
AND ORDER_DATE = ?
AND LAST_UPDATED = ?

This strategy does not rely on a specific field or fields being modified. However, if two updates occur which have no field in common then it will not detect the change which will lead to a silent concurrency violation.

Read Fields Locking Strategy

This strategy is similar to the Modified Field strategy except that is uses every field that was read by the application. This reduces the likelihood that two concurrent changes will not be detected.

This is configured in jbosscmp-jdbc.xml:

    <entity>
<ejb-name>Order</ejb-name>
<optimistic-locking>
<read-strategy/>
</optimistic-locking>
</entity>

Version Column Locking Strategy

The Version Column locking strategy avoids the risk of missed updates by adding a dedicated field to the EJB that is incremented every time it is updated. This is configured in jbosscmp-jdbc.xml:

    <entity>
<ejb-name>Order</ejb-name>
<optimistic-locking>
<version-column/>
<field-name>version_lock</field-name>
<column-name>VERSION_LOCK</column-name>
</optimistic-locking>
</entity>

The default field and column names are "version_lock" but this can be explicitly set in the configuation as shown above. The field type will always be a Java long, but the mapping to the database can optionally be specified using jdbc-type and sql-type elements.

Timestamp Column Locking Strategy

The Timestamp locking strategy uses the time of the last update to detect any conflicting updates. Every time the record is updated, new millisecond value is written to the database which is used to detect if the row has been updated. This strategy is effective for infrequent updates, but several changes happening together may be missed. This strategy is configured in jbosscmp-jdbc.xml as follows:

    <entity>
<ejb-name>Order</ejb-name>
<optimistic-locking>
<timestamp-column/>
<field-name>timestamp_lock</field-name>
<column-name>timestamp_lock</column-name>
<jdbc-type>TIMESTAMP</jdbc-type>
<sql-type>TIMESTAMP</sql-type>
</optimistic-locking>
</entity>

If the jdbc-type and sql-type are not specified in the configuration, they will default to the type mapping for java.util.Date.

Although a millisecond value is used in the update, the actual resolution of the timer may be different. Different JVM's update the current system time at different intervals (for example, every 10ms on Windows), and different databases with different date/time datatypes may truncate the stored value (for example, a SQL Server DATETIME column has a resoluion of 1/300s).

For SQL Server users, this strategy is based on the system time and should not be confused with the database's TIMESTAMP column type.

Generated Value Locking Strategy

This strategy operates in conjunction with an external key generator which provides a unique identifier for every update. This identifier is used to determine if the row has been modified. Care should be taken to use a key generator which can generate values at very low cost; for example, one that uses a database sequence to obtain values might result in two SQL calls for every update.

This is configured in jbosscmp-jdbc.xml as follows:

    <entity>
<ejb-name>Order</ejb-name>
<optimistic-locking>
<!-- the JNDI name of the key generator factory -->
<key-generator-factory>UUIDKeyGeneratorFactory</key-generator-factory>
<field-type>java.lang.String</field-type>

<field-name>generated_lock</field-name>
<column-name>generated_lock</column-name>
<jdbc-type>VARCHAR</jdbc-type>
<sql-type>CHAR(32)</sql-type>
</optimistic-locking>
</entity>

The key-generator-factory specifies the JNDI name of a key generator that implements org.jboss.ejb.plugins.keygenerator.KeyGeneratorFactory and the field-type element specifies the type of the keys that it generates.