Problems with replicating entity queries via JBC
brian.stansberry Nov 28, 2006 1:30 AMUsing JBC as a 2nd level cache leads to problems deserializing user classes when the Hibernate query cache is enabled. See http://jira.jboss.com/jira/browse/JBCLUSTER-150 for details.
This is really a JBC/Hibernate issue, although workarounds are possible in EJB3. Since any solution impacts EJB3, and any very short term workaround (i.e. for a December stacks release) is only possible in EJB3, I'm discussing the issue here.
Issue is JBC's classloader is from server/all/lib, and thus can't see and deserialize classes that are loaded from deploy. Deserialization happens when Hibernate's use of JBC as 2nd level cache causes replication. Specifically:
1) The Hibernate query cache is used. Here a user class could end up being used as an argument in a query. Hibernate replicates the actual query as the part of the FQN of a node in JBC.
2) A custom class is used as a field in an entity, and its not mapped as a component, but rather Hibernate treates it as a BLOB. The custom class would be replicated. (I haven't actually confirmed this failure mode, but it quite likely exists.)
Solutions:
A) Register a classloader with JBC for a region of the cache. JBC exposes an API to allow this to happen. The Hibernate/JBC integration code (part of Hibernate) can be updated to take advantage of this API. Hibernate already has the logical concept of different cache regions; the integration code can be updated to register the thread context classloader with JBC when a region is created.
EJB3 already partially overrides the standard Hibernate/JBC integration code, so as a short term workaround it's possible to fix this in EJB3, pending a later Hibernate release with this fixed.
Problems with solution A):
i) Hibernate has a "default" query cache region that will be shared by all queries that don't specifically name a region. There is no foolproof way to assign a specific classloader to this region, since it's storing to a JBC instance that may be shared between multiple EJB deployments. The cache region can be specified by users, although it's ugly:
@Entity @Cache (usage=CacheConcurrencyStrategy.TRANSACTIONAL) @NamedQueries({ @NamedQuery(name="account.highbalance.default",query="select account.balance from Account as account where account.accountHolder = ?1 and account.balance > ?2"), @NamedQuery(name="account.highbalance.namedregion",query="select account.balance from Account as account where account.accountHolder = ?1 and account.balance > ?2", hints={@QueryHint(name="org.hibernate.cacheRegion",value="AccountRegion")}) }) public class Account implements Serializable { ... }
The second query above uses the named region.
ii) With the query cache, the custom class is actually part of the Fqn of a JBC node, rather than part of its data map. I realized over the weekend that the existing JBC code for handling region-based classloaders doesn't handle custom classes in an Fqn. JIRA to remove this limitation is http://jira.jboss.com/jira/browse/JBCACHE-876, which should be pretty straightforward. But, fixing it requires JBC 1.4.1.GA, which is likely at least a couple weeks away.
Bringing us to solution..
B) Don't replicate the query cache.
Hibernate provides a hook to specifying the factory class used to set up any query cache. In persistence.xml we can specify a custom query cache factory that integrates with JBC, but which instructs JBC not to replicate any writes for that region of the cache. Thus the query cache is a local only cache, while entities are replicated. Hibernate also has an "UpdateTimestampsCache", which is used to trigger invalidations of query cache entries. AFAIK, there is no replication problem with this cache, and it needs to remain replicated.
I've pinged the Hibernate guys re: any issues w/ not replicating the query cache.
Solution B can also be implemented in the EJB3 code base pending a better solution in a Hibernate release. If there is an immediate requirement to get a workaround for this problem, it's the only way I see to go.
Another long term possibility is solution...
C) Like A), but now we create one JBC entity cache per deployment (i.e. per Hibernate session factory) rather than a single shared cache. This avoids the problem of deciding what classloader to use for the "default" query cache -- there's only one classloader per cache instance. With the JGroups multiplexer, having numerous JBC instances is a workable option. But going this route requires more thought.