Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
EclipseLink/Examples/JPA/DCN
Contents
Overview
EclipseLink supports a shared (L2) object cache that avoids database access for objects and their relationships. This cache is enabled by default which is normally not a problem, unless the database is modified directly by other applications, or by the same application on other servers in a clustered environment.
There are many solutions to caching in a shared environment, including:
- disable the shared cache
- only cache read-only objects
- set a cache invalidation timeout
- use refreshing on objects/queries when fresh data is required
- use optimistic locking (writes on stale data will fail, and will automatically invalidate the cache)
- using a distributed cache (such as Oracle TopLink Grid with Oracle Coherence)
- using cache coordination
- using database events to invalidate changed data
This example gives an overview of the database events option.
EclipseLink 2.4 adds support for a DatabaseEventListener to receive database events. EclipseLink provides an OracleChangeNotificationListener to integrate with Oracle's Database Event Notification support (also known as Query Change Notification). The Oracle database added this support in the 10.2 release, but did not fully enable the JDBC support for it until the 11.2 release. The OracleChangeNotificationListener uses Oracle's DCN support to listen to database row changes and invalidate the cache for the objects that are changed on the database. This allows for caching to be used in JPA, even if other applications, even non-Java applications are accessing and updating the same database. This can also be used as an alternative to cache coordination in a cluster.
Integrated support for other databases is not currently provided. If another database supports an event mechanism, or allows triggers to raise events, then it is possible to implement your own DatabaseEventListener to perform cache invalidation. In previous versions of the Oracle database it is possible to perform cache invalidation through triggers and Oracle AQ.
This example demonstrates enabling database event driven cache invalidation using Oracle DCN with the Oracle 11.2 database. The example runs in Java SE, but any other Java EE or EclipseLink supported environment should also work.
If you encounter any issues in running this example, please discuss, here
Prerequisites
The following software is required to run this example:
- Oracle 11.2 database - download link
- ant (1.7 was used, but other versions should also work) - download link
- EclipseLink 2.4 - download link
- Database event example - download link - SVN
Configuring Oracle Database Change Notification
Database events can be configured using persistence unit properties (in your persistence.xml). It can also be configured in code using a SessionCustomizer, or using System properties (which match the persistence unit properties). This example will use persistence unit properties.
<property name="eclipselink.cache.database-event-listener" value="org.eclipse.persistence.platform.database.oracle.dcn.OracleChangeNotificationListener" />
By default all entity classes are registered for database change notification. To exclude a class from change notification the @Cache annotation is used.
@Entity @Cache(databaseChangeNotificationType=DatabaseChangeNotificationType.NONE) public class Order { ... }
The database user must have the CHANGE NOTIFICATION
privilege granted on the database.
GRANT CHANGE NOTIFICATION TO SCOTT
Limitations, and how Oracle database change notification works
To register for Oracle DCN you must specify which table you wish to receive database change events for. When ever a row is inserted, updated, or deleted from the table, the database raises an event. The event contains the database ROWID of the affected rows.
The object cache in EclipseLink is on the entities Id, so EclipseLink must maintain an index on the ROWID in addition to the Id. EclipseLink's cache index support (also new in 2.4) is used for this. When objects are read, EclipseLink includes the ROWID in the select when using Oracle DCN. For inserts EclipseLink must select the ROWID for the inserted object to allow the cache index to be maintained.
EclipseLink must also avoid invalidating objects in the cache for the transactions processed by the same server, as the cache is already up to date. To do this EclipseLink selects the Oracle transaction id on each transaction that makes a modification, and ignores DCN events for the same transaction id.
This requires a little extra SQL, but it pretty minimal. EclipseLink only tracks the ROWID for the primary table, so if an update only updates a secondary table, EclipseLink will not invalidate the object. The same occurs when objects are removed or added to a OneToMany, ManyToMany or ElementCollection relationship, EclipseLink has no way to know this event results in a change to the entity object. This is easily solved through the use of optimistic locking. As long a you include an @Version in your entity, the version column in the primary table will always be updated, and the object will always be invalidated.
There is a delay from the point of the commit of the transaction, and the receiving and processing of the database change event. This delay is dependent on the database machine, its load, and the mid-tier machine and its load. In the example a sleep() is called after every commit to allow the event to be processed before the next test. The example has the SLEEP set at 1 second, which seems sufficient. Using 100 milliseconds normally also works, and sometimes even 1 millisecond is sufficient, so the database is normally quite fast in raising change events. If you get errors in running the example, you may need to increase the SLEEP duration.
Running the example
- Install Oracle database (or use existing database)
- Configure paths in <example>/build.xml (JDBC_LIB, JPA_LIB, ECLIPSELINK_LIB)
- Configure database URL and user/password in persistence.xml in <example>src/meta-inf/
- Ensure the database user has CHANGE NOTIFICATION privilege in the database
- Install ant (or uses existing ant install)
- Build the application
- Run "ant"
- this will compile the example code
- Run the example
- Run "ant example"
- This should give the following output.
[java] Starting example. [java] [java] [java] Selecting all customer on application #2. [java] [java] [java] Inserting new customer on application #1. [java] [java] [java] Selecting new customer on application #2. [java] [java] [java] Customer name on application #1: Test1 [java] Customer name on application #2: Test1 [java] [java] [java] Updating customer name to 'Test2' on application #1. [java] [java] [java] Selecting customer on application #2. [java] [java] [java] Customer name on application #1: Test2 [java] Customer name on application #2: Test2 [java] [java] [java] Updating customer name to 'Test3' through direct native SQL query on application #1. [java] [java] [java] Selecting customer on application #2. [java] [java] [java] Finding customer on application #1. [java] [java] [java] Customer name on application #1: Test3 [java] Customer name on application #2: Test3 [java] [java] [java] Updating customer name to 'Test4' through batch update query on application #1. [java] [java] [java] Selecting customer on application #2. [java] [java] [java] Finding customer on application #1. [java] [java] [java] Customer name on application #1: Test4 [java] Customer name on application #2: Test4 [java] [java] [java] Deleting customer on application #1. [java] [java] [java] Selecting customer on application #2. [java] [java] [java] Customer not found on application #2. [java] [java] [java] Inserting new customer 'Test5' on application #2. [java] [java] [java] Selecting customer on application #1. [java] [java] [java] Updating customer name to 'Test6' on application #1. [java] [java] [java] Selecting customer on application #2. [java] [java] [java] Customer name on application #1: Test6 [java] Customer name on application #2: Test6 [java] [java] [java] Example finished.