4 Integrating Hibernate and Coherence

This chapter describes how you can integrate Oracle Coherence with Hibernate, an object-relational mapping tool for Java environments. The functionality in Oracle Coherence and Hibernate can be combined such that Hibernate can act as the Coherence cache store or Coherence can act as the Hibernate L2 cache.

Notes:

You can find additional information about integrating Coherence and Hibernate in the Coherence Community projects at the following URL:

https://java.net/projects/cohhib

This chapter contains the following sections:

4.1 API for HibernateCacheStore and HibernateCacheLoader

Coherence includes a default entity-based cache store implementation, HibernateCacheStore, and a corresponding cache loader implementation, HibernateCacheLoader, in the com.tangosol.coherence.hibernate package.

Table 4-1 describes the different constructors for the HibernateCacheStore and HibernateCacheLoader classes. For more detailed technical information, see the Javadoc for these classes.

Table 4-1 Hibernate Cache Store and Cache Loader Classes

Class Name Description

HibernateCacheLoader() and HibernateCacheStore()

These constructors are the default constructors for creating a new instance of a cache loader or cache store. They do not create a Hibernate SessionFactory object. To create a Hibernate SessionFactory object when you are using these constructors, use the setSession() method.

HibernateCacheLoader(java.lang.String entityName) and HibernateCacheStore(java.lang.String entityName),

These constructors create a Hibernate SessionFactory object using the default Hibernate configuration (hibernate.cfg.xml) in the classpath.

HibernateCacheStore(java.lang.String entityName, java.lang.String sResource) and HibernateCacheStore(java.lang.String entityName, java.lang.String sResource)

These constructors create a Hibernate SessionFactory object based on the configuration file provided (sResource).

HibernateCacheLoader(java.lang.String entityName, java.io.File configurationFile) and HibernateCacheStore(java.lang.String entityName, java.io.File configurationFile),

These constructors create a Hibernate SessionFactory object based on the configuration file provided (configurationFile)

HibernateCacheStore(java.lang.String entityName, org.hibernate.SessionFactory sFactory) and HibernateCacheStore(java.lang.String entityName, org.hibernate.SessionFactory sFactory),

These constructors accept an entityName name and a Hibernate SessionFactory.


4.2 Using Hibernate as a Cache Store for Coherence

Hibernate can also be used as a cache store implementation for Coherence. Applications that use this approach typically have the following characteristics:

  • Use Coherence APIs for data access/management

  • Have simpler object models appropriate for the out-of-the-box Hibernate CacheStore implementation (note that applications with more complex object models can take advantage of custom CacheStore implementations)

  • Have simpler transactional requirements

  • Require high performance through the use of Coherence APIs, such as write-behind and aggregations.

4.2.1 Configuration Requirements

Hibernate entities accessed by using the HibernateCacheStore module must use the assigned ID generator and also have a defined ID property.

Disable the hibernate.hbm2ddl.auto property in the hibernate.cfg.xml file used by the HibernateCacheStore module to avoid excessive schema updates and possible lockups.

4.2.2 Configuring a Hibernate Cache Store Constructor

The following examples illustrate how to configure a simple HibernateCacheStore constructor, which accepts only an entity name. This configures Hibernate by using the default configuration path, which looks for a hibernate.cfg.xml file in the class path. You can also include a resource name or file specification for the hibernate.cfg.xml file as the second <init-param> (set the <param-type> element to java.lang.String for a resource name and java.io.File for a file specification). See the Javadoc for HibernateCacheStore for more information.

Example 4-1 illustrates a simple coherence-cache-config.xml file used to define a NamedCache cache object named TableA that caches instances of a Hibernate entity (com.company.TableA). To define more entity caches, add additional <cache-mapping> elements.

Example 4-1 Coherence Cache Configuration File for Hibernate

<?xml version="1.0"?>
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd">
  <caching-scheme-mapping>
    <cache-mapping>
      <cache-name>TableA</cache-name>
      <scheme-name>distributed-hibernate</scheme-name>
      <init-params>
        <init-param>
          <param-name>entityname</param-name>
          <param-value>com.company.TableA</param-value>
        </init-param>
      </init-params>
    </cache-mapping>
  </caching-scheme-mapping>

  <caching-schemes>
    <distributed-scheme>
      <scheme-name>distributed-hibernate</scheme-name>
      <backing-map-scheme>
        <read-write-backing-map-scheme>
          <internal-cache-scheme>
            <local-scheme></local-scheme>
          </internal-cache-scheme>

          <cachestore-scheme>
            <class-scheme>
              <class-name>
              com.tangosol.coherence.hibernate.HibernateCacheStore
              </class-name>
              <init-params>
                <init-param>
                  <param-type>java.lang.String</param-type>
                  <param-value>{entityname}</param-value>
                </init-param>
              </init-params>
            </class-scheme>
          </cachestore-scheme>
        </read-write-backing-map-scheme>
      </backing-map-scheme>
    </distributed-scheme>
  </caching-schemes>
</cache-config>

Example 4-2 illustrates that you can also use the predefined {cache-name} macro to eliminate the need for the <init-params> portion of the cache mapping.

Example 4-2 Coherence Cache Configuration File that Uses {cache-name} Macro

<?xml version="1.0"?>

<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd">
  <caching-scheme-mapping>
    <cache-mapping>
      <cache-name>TableA</cache-name>
      <scheme-name>distributed-hibernate</scheme-name>
    </cache-mapping>
  </caching-scheme-mapping>

  <caching-schemes>
    <distributed-scheme>
      <scheme-name>distributed-hibernate</scheme-name>
      <backing-map-scheme>
        <read-write-backing-map-scheme>
          <internal-cache-scheme>
            <local-scheme></local-scheme>
          </internal-cache-scheme>

          <cachestore-scheme>
            <class-scheme>
              <class-name>
              com.tangosol.coherence.hibernate.HibernateCacheStore
              </class-name>
              <init-params>
                <init-param>
                  <param-type>java.lang.String</param-type>
                  <param-value>com.company.{cache-name}</param-value>
                </init-param>
              </init-params>
            </class-scheme>
          </cachestore-scheme>
        </read-write-backing-map-scheme>
      </backing-map-scheme>
    </distributed-scheme>
  </caching-schemes>
</cache-config>

Example 4-3 illustrates that, if naming conventions allow, the mapping can be completely generalized to enable a cache mapping for any qualified class name (entity name).

Example 4-3 Sample coherence-cache-config.xml File with Generalized Mappings

<?xml version="1.0"?>

<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd">
  <caching-scheme-mapping>
    <cache-mapping>
      <cache-name>com.company.*</cache-name>
      <scheme-name>distributed-hibernate</scheme-name>
    </cache-mapping>
  </caching-scheme-mapping>

  <caching-schemes>
    <distributed-scheme>
      <scheme-name>distributed-hibernate</scheme-name>
      <backing-map-scheme>
        <read-write-backing-map-scheme>
          <internal-cache-scheme>
            <local-scheme></local-scheme>
          </internal-cache-scheme>

          <cachestore-scheme>
            <class-scheme>
              <class-name>
              com.tangosol.coherence.hibernate.HibernateCacheStore
              </class-name>
              <init-params>
                <init-param>
                  <param-type>java.lang.String</param-type>
                  <param-value>{cache-name}</param-value>
                </init-param>
              </init-params>
            </class-scheme>
          </cachestore-scheme>
        </read-write-backing-map-scheme>
      </backing-map-scheme>
    </distributed-scheme>
  </caching-schemes>
</cache-config>

4.2.3 Creating a Hibernate Cache Store

While the provided HibernateCacheStore module provides a solution for most entity-based caches, there may be cases where an application-specific CacheStore module is necessary. For example, for providing parameterized queries, or including or post-processing query results.

4.2.3.1 Reentrant Calls

In a cache store-backed cache implementation, when the application thread accesses cached data, the cache operations may trigger a call to the associated CacheStore implementation by using the managing CacheService API. The CacheStore must not call back into the CacheService API. This implies, indirectly, that Hibernate should not attempt to access cache data. Therefore, all methods in the CacheLoader or CacheStore implementation should be careful to call Session.setCacheMode(CacheMode.IGNORE) method to disable cache access. Alternatively, the Hibernate configuration can be cloned (either programmatically or by using the hibernate.cfg.xml file), with CacheStore implementations using the version with the cache disabled.

4.2.4 Extending the Hibernate Cache Store Functionality

In some cases, you may want to extend the Hibernate cache store with application-specific functionality. The most obvious reason for this is to take advantage of a preexisting, programmatically configured SessionFactory instance.

4.2.5 JDBC Isolation Level

In cases where all access to a database is through Coherence, cache store modules naturally enforce ANSI-style repeatable read isolation as read operations, and write operations are executed serially on a per-key basis (by using the Partitioned Cache Service). Increasing database isolation above the repeatable read level does not yield increased isolation because cache store operations might span multiple partitioned cache nodes (and thus multiple database transactions). Using database isolation levels below the repeatable read level does not result in unexpected anomalies, and might reduce processing load on the database server.

4.2.6 Fault-Tolerance for Hibernate Cache Store Operations

For single-cache-entry updates, cache store operations are fully fault-tolerant in that the cache and database are guaranteed to be consistent during any server failure (including failures during partial updates). While the mechanisms for fault-tolerance vary, this is true for both write-through and write-behind caches.

Coherence does not support two-phase cache store operations across multiple cache store instances. In other words, if two cache entries are updated, triggering calls to cache store modules sitting on separate servers, it is possible for one database update to succeed and for the other to fail. In this case, you might want to use a cache-aside architecture (updating the cache and database as two separate components of a single transaction) with the application server transaction manager. In many cases, it is possible to design the database schema to prevent logical commit failures (but obviously not server failures). Write-behind caching avoids this issue because put operations are not affected by database behavior (and the underlying issues have been addressed earlier in the design process).

4.2.7 Using Fully Cached Data Sets

There are two scenarios where using fully cached data sets would be advantageous. One is when you are performing distributed queries on the cache; the other is when you want to provide continued application processing despite a database failure.

4.2.7.1 Distributed Queries

Distributed queries offer the potential for lower latency, higher throughput, and less database server load, as opposed to executing queries on the database server. For set-oriented queries, the data set must be entirely cached to produce correct query results. More precisely, for a query issued against the cache to produce correct results, the query must not depend on any uncached data.

Distributed queries enable you to create hybrid caches. For example, it is possible to combine two uses of NamedCache: a fully cached size-limited data set for querying (for example, the data for the most recent week), and a partially cached historical data set used for singleton read operations. This approach avoids data duplication and minimizes memory usage.

While fully cached data sets are usually bulk-loaded during application startup (or on a periodic basis), cache store integration can be used to ensure that both cache and database are kept fully synchronized.

4.2.7.2 Detached Processing

Another reason for using fully cached data sets is to provide the ability to continue application processing even if the underlying database fails. Using write-behind caching extends this mode of operation to support full read-write applications. With write-behind, the cache becomes (in effect) the temporary system of record. Should the database fail, updates are queued in Coherence until the connection is restored. At this point, all cache changes are sent to the database.

4.3 Using Coherence as the Hibernate L2 Cache Provider

Using Coherence as a Hibernate L2 cache provider enables multiple JVMs running the same Hibernate application to share an L2 cache. The use of Coherence caching in this case is controlled by Hibernate. You should have a good understanding of Hibernate L2 caching to successfully use this provider. For more information on Hibernate as an L2 cache, see "Improving Performance" chapter of the Hibernate Reference Manual:

http://www.hibernate.org/docs.html

This may be a good fit for applications that have these characteristics:

  • Use Hibernate APIs for data access/management.

  • Have large/complex object models.

  • Have complicated transactional requirements.

  • Have a large cluster of application servers running Hibernate that access the same database

Hibernate supports three primary forms of caching:

  • Session cache

  • L2 cache

  • Query cache

The session cache is responsible for caching records within a Hibernate Session. A Hibernate Session is a transaction-level cache of persisted data, potentially spanning multiple database transactions, and typically scoped on a per-thread basis. As a nonclustered cache (by definition), the session cache is managed entirely by Hibernate.

The L2 and query caches span multiple transactions, and support the use of Coherence as a cache provider. The L2 cache is responsible for caching records across multiple sessions (for primary key lookups). The query cache caches the result sets generated by Hibernate queries. Hibernate manages data in an internal representation in the L2 and query caches, meaning that these caches are usable only by Hibernate. For more information, see the Hibernate Reference Documentation (shipped with Hibernate), specifically the section on the Second Level Cache.

4.3.1 Configuring Coherence as the Hibernate L2 Cache

To use the Coherence caching provider for Hibernate, specify the Coherence provider class in the hibernate.cache.provider_class property. Typically, this is configured in the default Hibernate configuration file, hibernate.cfg.xml. Example 4-4 illustrates the property element calling CoherenceCacheProvider (com.tangosol.coherence.hibernate.CacheProvider) as the value of the hibernate.cache.provider_class property.

Example 4-4 Specifying a Coherence Provider Class

<property name="hibernate.cache.provider_class">com.tangosol.coherence.hibernate.CoherenceCacheProvider</property>

The coherence-hibernate.jar file (found in the lib/ subdirectory) must be added to the application class path.

Hibernate provides the configuration property hibernate.cache.use_minimal_puts, which optimizes cache access for clustered caches by increasing cache read operations and minimizing cache update operations. The Coherence caching provider enables this by default. Setting this property to false might increase overhead for cache management and also increase the number of transaction rollbacks.

The Coherence caching provider includes a setting for how long a lock acquisition should be attempted before timing out. Use the Java property tangosol.coherence.hibernate.lockattemptmillis to specify the value. The default is one minute.

4.3.2 Specifying a Coherence Cache Topology

By default, the Coherence caching provider uses a custom cache configuration located in the coherence-hibernate.jar file named config/hibernate-cache-config.xml. This configuration file defines cache mappings for Hibernate L2 caches. If desired, you can specify an alternative cache configuration resource for Hibernate L2 caches by using the tangosol.coherence.hibernate.cacheconfig Java property. You can configure this property to point to the application's main coherence-cache-config.xml file if the mappings are properly configured. It can be beneficial to use dedicated cache service(s) to manage Hibernate-specific caches to ensure that any cache store modules do not cause reentrant calls back into Coherence-managed Hibernate L2 caches.

In the scheme mapping section of the Coherence cache configuration file, the hibernate.cache.region_prefix property can specify a cache topology. For example, if the cache configuration file includes a wildcard mapping for near-*, and the Hibernate region prefix property is set to near-, then all Hibernate caches are named using the near- prefix, and use the cache scheme mapping specified for the near-* cache name pattern.

It is possible to specify a cache topology per entity by creating a cache mapping based on the combined prefix and qualified entity name (for example, near-com.company.EntityName); or equivalently, by providing an empty prefix and specifying a cache mapping for each qualified entity name.

L2 caches should be size-limited to avoid excessive memory usage. Query caches in particular must be size-limited because the Hibernate API does not provide any means of controlling the query cache other than a complete eviction.

4.3.3 Choosing a Cache Concurrency Strategy

Hibernate generally emphasizes the use of optimistic concurrency for both cache and database. With optimistic concurrency in particular, transaction processing depends on having accurate data available to the application at the beginning of the transaction. If the data is inaccurate, then commit processing detects that the transaction was dependent on incorrect data, and the transaction is not committed. While most optimistic transactions must adjust to changes to underlying data by other processes, the use of caching adds the possibility of the cache itself being stale. Hibernate provides several cache concurrency strategies to control updates to the L2 cache. While this is less of an issue for Coherence due to support for clusterwide coherent caches, the appropriate selection of cache concurrency strategy aids application efficiency.

Note that cache configuration strategies can be specified at the table level. Generally, the strategy should be specified in the mapping file for the class.

For mixed read-write activity, the read-write strategy is recommended. The transactional strategy is implemented similarly to the nonstrict-read-write strategy, and relies on the optimistic concurrency features of Hibernate. A nonstrict-read-write strategy is appropriate when the application only occasionally updates data, and does not require strict transaction isolation. Note that the nonstrict-read-write may deliver better performance if its impact on optimistic concurrency is acceptable.

For read-only caching, use the nonstrict-read-write strategy if the underlying database data might change, but slightly stale data is acceptable. If the underlying database data never changes, use the read-only strategy.

4.3.4 Query Cache

To cache query results, set the hibernate.cache.use_query_cache property to true. Then, whenever issuing a query that must cache its results, use the Query.setCacheable(true) method. As org.hibernate.cache.QueryKey instances in Hibernate might not be binary-comparable (due to nondeterministic serialization of unordered data members), use a size-limited local or replicated cache to store query results (this forces the use of the hashcode() or equals() methods to compare keys).

The default query cache name is org.hibernate.cache.StandardQueryCache (unless a default region prefix is provided; in this case [prefix]. is prefixed to the cache name). Use the cache configuration file to map this cache name to a local or replicated topology, or to explicitly provide an appropriately-mapped region name when querying.

4.3.5 Fault-Tolerance for Hibernate L2 Caches

The Hibernate L2 cache protocol supports full fault-tolerance during client or server failure. With the read-write cache concurrency strategy, Hibernate locks items out of the cache at the start of an update transaction, meaning that client-side failures simply result in uncached entities and an uncommitted transaction. Server-side failures are handled transparently by Coherence (dependent on the specified data backup count).

4.3.6 Deployment

When used with application servers that do not have a unified class loader, the Coherence caching provider must be deployed as part of the application so that it can use the application-specific class loader (required to serialize and deserialize objects).