Hi. Can anyone tell me what is wrong with this setup. We've had lots of problems in our application when trying to cache components with composite id's into the second level cache (ehcache or HashTableCache). I've extracted an example set-up below, which describes the problem.
There's two tables with primary keys that consist of two integer fields. They are in no way associated with each other, but they keys can collapse (that is, keys in both table can have same values, pretty normal situation). After loading and cacheing the values from the first table, Hibernate tries to put the values of second table into the cache. This fails with ClassCastException.
Information:
Hibernate version:
3.1
Mapping documents:
Code:
<hibernate-mapping>
<class name="oo.pojo.TableA" table="TableA">
<composite-id name="id" class="oo.pojo.TableAId">
<key-property name="A" type="integer">
<meta attribute="use-in-equals">true</meta>
<column name="A" />
</key-property>
<key-property name="B" type="integer">
<meta attribute="use-in-equals">true</meta>
<column name="B" />
</key-property>
</composite-id>
</class>
<class name="oo.pojo.TableB" table="TableB">
<composite-id name="id" class="oo.pojo.TableBId">
<key-property name="C" type="integer">
<meta attribute="use-in-equals">true</meta>
<column name="C" />
</key-property>
<key-property name="D" type="integer">
<meta attribute="use-in-equals">true</meta>
<column name="D" />
</key-property>
</composite-id>
</class>
</hibernate-mapping>
<hibernate-configuration>
<session-factory>
...
<property name="hibernate.show_sql">true</property>
<property name="hibernate.generate_statistics">true</property>
<property name="hibernate.cache.use_structured_entries">true</property>
<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
<mapping resource="oo/mapping/test3.hbm.xml"/>
<class-cache class="oo.pojo.TableA" region="read-write" usage="read-write"/>
<class-cache class="oo.pojo.TableB" region="read-write" usage="read-write"/>
</session-factory>
</hibernate-configuration>
The component classes are generated from the mapping file with Hibernate tools.
Code between sessionFactory.openSession() and session.close():Code:
Criteria bCriteria = session.createCriteria(TableB.class);
List<TableB> bList = bCriteria.list();
for (TableB b : bList) {
System.out.println(b.getId());
}
Criteria aCriteria = session.createCriteria(TableA.class);
List<TableA> aList = aCriteria.list();
for (TableA a : aList) {
System.out.println(a.getId());
}
Full stack trace of any exception that occurs:Code:
org.hibernate.PropertyAccessException: IllegalArgumentException occurred calling getter of oo.pojo.TableBId.C
at org.hibernate.property.BasicPropertyAccessor$BasicGetter.get(BasicPropertyAccessor.java:171)
at org.hibernate.tuple.AbstractComponentTuplizer.getPropertyValue(AbstractComponentTuplizer.java:121)
at org.hibernate.tuple.AbstractComponentTuplizer.getPropertyValues(AbstractComponentTuplizer.java:133)
at org.hibernate.tuple.PojoComponentTuplizer.getPropertyValues(PojoComponentTuplizer.java:88)
at org.hibernate.type.ComponentType.getPropertyValues(ComponentType.java:307)
at org.hibernate.type.ComponentType.isEqual(ComponentType.java:111)
at org.hibernate.cache.CacheKey.equals(CacheKey.java:51)
at java.util.Hashtable.get(Hashtable.java:339)
at org.hibernate.cache.HashtableCache.get(HashtableCache.java:31)
at org.hibernate.cache.ReadWriteCache.put(ReadWriteCache.java:153)
at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:153)
at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:842)
at org.hibernate.loader.Loader.doQuery(Loader.java:717)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:224)
at org.hibernate.loader.Loader.doList(Loader.java:2145)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2029)
at org.hibernate.loader.Loader.list(Loader.java:2024)
at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:94)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1552)
at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:283)
at oo.TestCase.main(TestCase.java:37)
Caused by: java.lang.IllegalArgumentException: java.lang.ClassCastException@b2fb1e
at sun.reflect.GeneratedMethodAccessor3.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.hibernate.property.BasicPropertyAccessor$BasicGetter.get(BasicPropertyAccessor.java:145)
... 20 more
Name and version of the database you are using:HSQLDB 1.8.0, MySQL 5.0, DB2
The generated SQL (show_sql=true):Code:
Hibernate: select this_.C as C1_0_, this_.D as D1_0_ from TableB this_
Hibernate: select this_.A as A0_0_, this_.B as B0_0_ from TableA this_
Debug level Hibernate log excerpt:A lot of log before, the few lines before exception:
Code:
12:16:12,518 DEBUG Loader:839 - total objects hydrated: 9
12:16:12,518 DEBUG TwoPhaseLoad:104 - resolving associations for [oo.pojo.TableA#component[A,B]{A=1, B=1}]
12:16:12,519 DEBUG TwoPhaseLoad:132 - adding entity to second-level cache: [oo.pojo.TableA#component[A,B]{A=1, B=1}]
12:16:12,519 DEBUG ReadWriteCache:148 - Caching: oo.pojo.TableA#oo.pojo.TableAId@5b0f
12:16:12,519 ERROR BasicPropertyAccessor:167 - IllegalArgumentException in class: oo.pojo.TableBId, getter method of property: C
I've been debugging this a lot and it seems to me that the problem is in the equals()-method of CacheKey. Since the hashcodes of the two keys collapse (the hashcode for the CacheKey is calculated straigth from the properties of the composite id and when the two tables have same values as their primary key, their hashcodes are same), code enters the equals()-method of CacheKey.
In org.hibernate.cache.CacheKey.equals()-method, in following line of code:
Code:
return type.isEqual(key, that.key, entityMode) &&
entityOrRoleName.equals(that.entityOrRoleName);
Isn't that in wrong order? Shouldn't CacheKey first check if the entities really are of the same component and only then check if the properties are the same?
Or am I totally on the wrong track here? And in that case, what would be the right way to handle this kind of situation?
Thanks for any input on this,
Teppo