| Oracle® Coherence Integration Guide for Oracle Coherence Release 3.5 Part Number E14537-01 |
|
|
View PDF |
Hibernate is an object/relational mapping tool for Java environments. The functionality in Oracle Coherence and Hibernate can be combined in several ways.
Coherence includes a default entity-based CacheStore implementation, HibernateCacheStore (and a corresponding CacheLoader implementation, HibernateCacheLoader) in the com.tangosol.coherence.hibernate package.
The HibernateCacheStore and HibernateCacheLoader API provide the following constructors:
HibernateCacheLoader(), HibernateCacheStore()—Default constructors which creates a new instance of a CacheLoader or CacheStore. They do not create a SessionFactory. To create a SessionFactory, use the setSession() method.
HibernateCacheLoader(java.lang.String entityName), HibernateCacheStore(java.lang.String entityName)—Creates a SessionFactory using the default Hibernate configuration (hibernate.cfg.xml) in the classpath.
HibernateCacheStore(java.lang.String entityName, java.lang.String sResource), HibernateCacheStore(java.lang.String entityName, java.lang.String sResource)—Creates a SessionFactory based on the configuration file provided (sResource)
HibernateCacheLoader(java.lang.String entityName, java.io.File configurationFile), HibernateCacheStore(java.lang.String entityName, java.io.File configurationFile)—Creates a SessionFactory based on the configuration file provided (configurationFile)
HibernateCacheStore(java.lang.String entityName, org.hibernate.SessionFactory sFactory), HibernateCacheStore(java.lang.String entityName, org.hibernate.SessionFactory sFactory)—Cache store constructors which accept an entityName and a Hibernate session factory.
More detailed technical information may be found in the Javadoc for the implementing classes.
The examples below show a simple HibernateCacheStore constructor, accepting only an entity name. This configures Hibernate using the default configuration path, which looks for a hibernate.cfg.xml file in the classpath. There is also the ability to pass in 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 HibernateCacheStore for more details.
Example 3-1 illustrates a simple coherence-cache-config.xml file used to define a NamedCache named TableA which caches instances of a Hibernate entity (com.company.TableA). To add additional entity caches, add additional <cache-mapping> elements.
Example 3-1 Sample coherence-cache-config.xml File for Hibernate
<?xml version="1.0"?> <!DOCTYPE cache-config SYSTEM "cache-config.dtd"> <cache-config> <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 3-2 illustrates that it is also possible to use the pre-defined {cache-name} macro to eliminate the need for the <init-params> portion of the cache mapping.
Example 3-2 Sample coherence-cache-config.xml File that Uses {cache-name} Macro
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<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 3-3 illustrates that, if naming conventions allow, the mapping may be completely generalized to allow a cache mapping for any qualified class name (entity name).
Example 3-3 Sample coherence-cache-config.xml File with Generalized Mappings
<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">
<cache-config>
<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>
Hibernate entities accessed by using the HibernateCacheStore module must use the "assigned" ID generator and also have a defined ID property.
Be sure to disable the hibernate.hbm2ddl.auto property in the hibernate.cfg.xml used by the HibernateCacheStore, as this may cause excessive schema updates (and possible lockups).
In cases where all access to a database is through Coherence, CacheStore modules naturally enforce ANSI-style Repeatable Read isolation as reads and writes are executed serially on a per-key basis (by using the Partitioned Cache Service). Increasing database isolation above Repeatable Read do not yield increased isolation as CacheStore operations may span multiple Partitioned Cache nodes (and thus multiple database transactions). Using database isolation levels below Repeatable Read do not result in unexpected anomalies, and may reduce processing load on the database server.
For single-cache-entry updates, CacheStore 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 CacheStore operations across multiple CacheStore instances. In other words, if two cache entries are updated, triggering calls to CacheStore modules sitting on separate servers, it is possible for one database update to succeed and for the other to fail. In this case, it may be preferable 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 as "puts" are not affected by database behavior (and the underlying issues have been addressed earlier in the design process).
In some cases, it may be desired to extend the HibernateCacheStore with application-specific functionality. The most obvious reason for this is to leverage a pre-existing, programmatically-configured SessionFactory instance.
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, providing parameterized queries or including or post-processing of query results.
In a CacheStore-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. 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 CacheLoader or CacheStore should be careful to call Session.setCacheMode(CacheMode.IGNORE) to disable cache access. Alternatively, the Hibernate configuration may be cloned (either programmatically or by using hibernate.cfg.xml), with CacheStore implementations using the version with the cache disabled.
There are two scenarios where using fully-cached datasets 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.
Distributed queries offer the potential for lower latency, higher throughput and less database server load compared to executing queries on the database server. For set-oriented queries, the dataset 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 a NamedCache: a fully cached size-limited dataset for querying (for example, the data for the most recent week), and a partially cached historical dataset used for singleton reads. This is a good approach to avoid data duplication and minimize memory usage.
While fully cached datasets are usually bulk-loaded during application startup (or on a periodic basis), CacheStore integration may be used to ensure that both cache and database are kept fully synchronized.
Another reason for using fully-cached datasets is to provide the ability to continue application processing even if the underlying database goes down. 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.
Coherence can be used as the L2 cache provider for Hibernate.
Hibernate supports three primary forms of caching:
Session cache
L2 cache
Query cache
The Session cache is responsible for caching records within a Session (a Hibernate transaction, potentially spanning multiple database transactions, and typically scoped on a per-thread basis). As a non-clustered 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 details, see the Hibernate Reference Documentation (shipped with Hibernate), specifically the section on the Second Level 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 3-4 illustrates the property element calling CoherenceCacheProvider as the hibernate.cache.provider_class.
Example 3-4 Specifying a Coherence Provider Class
<property name="hibernate.cache.provider_class">com.tangosol.coherence.hibernate.CoherenceCacheProvider</property>
The file coherence-hibernate.jar (found in the lib/ subdirectory) must be added to the application classpath.
Hibernate provides the configuration property hibernate.cache.use_minimal_puts, which optimizes cache access for clustered caches by increasing cache reads and decreasing cache updates. This is enabled by default by the Coherence Cache Provider. Setting this property to false may 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. This may be specified by the Java property tangosol.coherence.hibernate.lockattemptmillis. The default is one minute.
By default, the Coherence Caching Provider uses a custom cache configuration located in the coherence-hibernate.jar named config/hibernate-cache-config.xml. This configuration file is used to define cache mappings for Hibernate L2 caches. If desired, an alternative cache configuration resource may be specified for Hibernate L2 caches by using the tangosol.coherence.hibernate.cacheconfig Java property. It is possible to configure this property to point to the application's main coherence-cache-config.xml file if mappings are properly configured. It may be beneficial to use dedicated cache service(s) to manage Hibernate-specific caches to ensure that any CacheStore modules do not cause re-entrant 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 be used to 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.
Also, L2 caches should be size-limited to avoid excessive memory usage. Query caches in particular must be size-limited as the Hibernate API does not provide any means of controlling the query cache other than a complete eviction.
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 fails to commit. While most optimistic transactions must cope with 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 cluster-wide coherent caches, appropriate selection of cache concurrency strategy aids application efficiency.
Note that cache configuration strategies may 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. Note that 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 may change, but slightly stale data is acceptable. If the underlying database data never changes, use the read-only strategy.
To cache query results, set the hibernate.cache.use_query_cache property to "true". Then whenever issuing a cacheable query, use Query.setCacheable(true) to enable caching of query results. As org.hibernate.cache.QueryKey instances in Hibernate may not be binary-comparable (due to non-deterministic 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 prepended to the cache name). Use the cache configuration file to map this cache name to a Local or Replicated topology, or explicitly provide an appropriately-mapped region name when querying.
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).