Hello,
I would like to use hibernate as persistence-framework to access a database with special requirements:
- The database has many similar tables with the same structure: one "parent"-table and a "subentity"-table. The subentity has a foreign key to the parent to achieve a collection-mapping with an one-to-many association of the subentities.
- All couples of parent-/subentity tables have to be accessed by the same "physical" entity-class!
Hibernate has a feature called entity-mode="dynamic-map". This uses a java Map as entity-class with an individual entity-name.
I created two tests:
- "pojo" uses the standard pojo entity-mode with an own entity-class and is working correct.
pojo-mapping:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="hibernatetest.pojo.PojoParentEntity" table="PARENT_ENTITY">
<id name="ID" column="PARENT_ID" type="long">
<generator class="native" />
</id>
<property name="HNR" column="PARENT_HNR" type="long" />
<property name="VARIANT" column="PARENT_VARIANT" type="string" />
<property name="PARENT_VALUE" column="PARENT_VALUE" type="string" />
<properties name="IDANDHNR" unique="true">
<property name="ID" column="PARENT_ID" type="long" insert="false"
update="false" />
<property name="HNR" column="PARENT_HNR" type="long" insert="false"
update="false" />
</properties>
<map name="SUBENTITIES" cascade="all" inverse="true" lazy="false">
<key not-null="true" property-ref="IDANDHNR">
<column name="SUB_PARENT_ID" not-null="true" />
<column name="SUB_HNR" not-null="true" />
</key>
<map-key column="SUB_NAME" type="string" />
<one-to-many class="hibernatetest.pojo.PojoSubEntity" />
</map>
</class>
<class name="hibernatetest.pojo.PojoSubEntity" table="SUB_ENTITY">
<id name="ID" column="SUB_ID" type="long">
<generator class="native" />
</id>
<many-to-one name="PARENT" property-ref="IDANDHNR" lazy="false">
<column name="SUB_PARENT_ID" />
<column name="SUB_HNR" />
</many-to-one>
<property name="NAME" column="SUB_NAME" type="string" />
<property name="SUB_VALUE" column="SUB_VALUE" type="string" />
</class>
</hibernate-mapping>
- "map" tries to achieve the same behaviour with entity-mode="dynamic-map" but fails
dynamic-map-mapping
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class entity-name="ParentEntity" table="PARENT_ENTITY" lazy="false">
<tuplizer entity-mode="dynamic-map"
class="hibernatetest.EntityTuplizer" />
<id name="ID" column="PARENT_ID" type="long">
<generator class="native" />
</id>
<property name="HNR" column="PARENT_HNR" type="long" />
<property name="VARIANT" column="PARENT_VARIANT" type="string" />
<property name="PARENT_VALUE" column="PARENT_VALUE" type="string" />
<properties name="IDANDHNR" unique="true">
<property name="ID" column="PARENT_ID" type="long" insert="false"
update="false" />
<property name="HNR" column="PARENT_HNR" type="long" insert="false"
update="false" />
</properties>
<map name="SUBENTITIES" cascade="all" inverse="true" lazy="false">
<key not-null="true" property-ref="IDANDHNR" unique="true">
<column name="SUB_PARENT_ID" not-null="true" />
<column name="SUB_HNR" not-null="true" />
</key>
<map-key column="SUB_NAME" type="string" />
<one-to-many entity-name="SubEntity" />
</map>
</class>
<class entity-name="SubEntity" table="SUB_ENTITY">
<id name="ID" column="SUB_ID" type="long">
<generator class="native" />
</id>
<many-to-one name="PARENT" entity-name="ParentEntity"
property-ref="IDANDHNR" lazy="false">
<column name="SUB_PARENT_ID" />
<column name="SUB_HNR" />
</many-to-one>
<property name="NAME" column="SUB_NAME" type="string" />
<property name="SUB_VALUE" column="SUB_VALUE" type="string" />
</class>
</hibernate-mapping>
MapParentEntity.java
Code:
package hibernatetest.map;
import static java.util.Collections.EMPTY_SET;
import hibernatetest.ParentEntity;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.hibernate.tuple.DynamicMapInstantiator;
public class MapParentEntity extends HashMap<String, Object> implements ParentEntity {
private static final long serialVersionUID = 1L;
private static final String SUBENTITY_TYPE = "SubEntity";
private static final String PARENTENTITY_TYPE = "ParentEntity";
public MapParentEntity() {
super();
put(DynamicMapInstantiator.KEY, PARENTENTITY_TYPE);
}
// ---------------------
// ParentEntity interface
//
public Object getProperty(String name) {
return get(name);
}
public void setProperty(String name, Object value) {
put(name, value);
}
public Object getSubPropertyValue(String name) {
return getSUBENTITIES().get(name).get(SUB_VALUE);
}
@SuppressWarnings("unchecked")
public void setSubPropertyValue(String name, String value) {
Map<String, Map<String, Object>> subentities = getSUBENTITIES();
Map<String, Object> subentity = subentities.get(name);
if (subentity == null) {
subentity = new HashMap<String, Object>();
subentity.put(PARENT, this);
subentity.put(NAME, name);
subentity.put(SUB_VALUE, value);
subentity.put(DynamicMapInstantiator.KEY, SUBENTITY_TYPE);
subentities.put(name, subentity);
} else {
subentity.put(SUB_VALUE, value);
}
}
@SuppressWarnings("unchecked")
public Set<String> getSubPropertyNames() {
Map<String, Map<String, Object>> temp = getSUBENTITIES();
if (temp != null) {
return temp.keySet();
}
return EMPTY_SET;
}
// ---------------------
// private
//
@SuppressWarnings("unchecked")
private Map<String, Map<String, Object>> getSUBENTITIES() {
Map<String, Map<String, Object>> subentitiesMap = (Map<String, Map<String, Object>>) get(SUBENTITIES);
if (subentitiesMap == null) {
subentitiesMap = new HashMap<String, Map<String, Object>>();
put(SUBENTITIES, subentitiesMap);
}
return subentitiesMap;
}
}
Saving a parentEntity with subEntities throws a NullPointerException inside the MapAccessor class.
save exception:
Code:
java.lang.NullPointerException
at org.hibernate.property.MapAccessor$MapGetter.get(MapAccessor.java:67)
at org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValue(AbstractComponentTuplizer.java:64)
at org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValues(AbstractComponentTuplizer.java:70)
at org.hibernate.type.ComponentType.getPropertyValues(ComponentType.java:353)
at org.hibernate.type.ComponentType.getHashCode(ComponentType.java:184)
at org.hibernate.engine.CollectionKey.generateHashCode(CollectionKey.java:57)
at org.hibernate.engine.CollectionKey.<init>(CollectionKey.java:45)
at org.hibernate.engine.CollectionKey.<init>(CollectionKey.java:31)
at org.hibernate.event.def.AbstractFlushingEventListener.postFlush(AbstractFlushingEventListener.java:342)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:28)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at hibernatetest.AbstractSaveTest.testSave(AbstractSaveTest.java:30)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.springframework.test.context.junit4.SpringTestMethod.invoke(SpringTestMethod.java:163)
at org.springframework.test.context.junit4.SpringMethodRoadie.runTestMethod(SpringMethodRoadie.java:233)
at org.springframework.test.context.junit4.SpringMethodRoadie$RunBeforesThenTestThenAfters.run(SpringMethodRoadie.java:333)
at org.springframework.test.context.junit4.SpringMethodRoadie.runWithRepetitions(SpringMethodRoadie.java:217)
at org.springframework.test.context.junit4.SpringMethodRoadie.runTest(SpringMethodRoadie.java:197)
at org.springframework.test.context.junit4.SpringMethodRoadie.run(SpringMethodRoadie.java:143)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.invokeTestMethod(SpringJUnit4ClassRunner.java:142)
at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Accessing the subEntities of a successfully loaded parentEntity throws a StackOverflowError
load exception:
Code:
java.lang.StackOverflowError
at java.lang.Long.stringSize(Long.java:306)
at java.lang.Long.toString(Long.java:242)
at java.lang.Long.toString(Long.java:100)
at java.lang.String.valueOf(String.java:2734)
at java.lang.Long.toString(Long.java:733)
at java.lang.String.valueOf(String.java:2615)
at java.lang.StringBuffer.append(StringBuffer.java:220)
at java.util.AbstractMap.toString(AbstractMap.java:598)
at java.lang.String.valueOf(String.java:2615)
at java.lang.StringBuffer.append(StringBuffer.java:220)
at java.util.AbstractMap.toString(AbstractMap.java:598)
at java.lang.String.valueOf(String.java:2615)
at java.lang.StringBuffer.append(StringBuffer.java:220)
at java.util.AbstractMap.toString(AbstractMap.java:598)
at org.hibernate.collection.PersistentMap.toString(PersistentMap.java:252)
at java.lang.String.valueOf(String.java:2615)
at java.lang.StringBuffer.append(StringBuffer.java:220)
at java.util.AbstractMap.toString(AbstractMap.java:598)
at java.lang.String.valueOf(String.java:2615)
at java.lang.StringBuffer.append(StringBuffer.java:220)
at java.util.AbstractMap.toString(AbstractMap.java:598)
at java.lang.String.valueOf(String.java:2615)
at java.lang.StringBuffer.append(StringBuffer.java:220)
at java.util.AbstractMap.toString(AbstractMap.java:598)
at [...]
I don't know what is wrong. I read that dynamic-map is experimental. Maybe there are some bugs with "dynamic-map"?
If someone would like to show me, what is wrong with my test, I can send the whole test-project.
Yours sincerely
Lars