Dear fellow user,
The question I have might be related to this other posting ( https://forum.hibernate.org/viewtopic.php?f=1&t=1000211 ). The question was never answered there, so I will try to formulate it a bit differently and share the code I wrote in an attempt to find a solution myself.
I think the bottomline of my problem is that I have a field (lets name it 'theValue') that in Java is typed as java.lang.Object. Based on the enumeration value in another field (lets name it 'theType') we know how to treat the content of theValue field. In Java we use generics to specify the type. Because this generic typing information is not available at runtime, the enumerated value in theType field is all we have to work with.
One way to solve my problem would be to make subclasses for all possible theType values. I'd rather not do this because those classes do not add any value / information to my data model, thus I would make this data model change pure to entertain hibernate. I hope we can find a way to make hibernate able to work with the information available.
Two more things worthwhile to mention:
- theValue can contain basic types like String, Boolean or Integer, as well as references to other entities
- in reality theValue is actually a List of values (all of the same type), which is what is depicted in the code below
The Java code and mappings I wrote consists of:
The definition of some nothing-special-hibernate-managed-entity, Component.java:
Code:
public class Component
{
/**
* JPA persistence ID, may or may not be used by the persistence implementation. Not to be used
* by application logic!
*/
@SuppressWarnings("unused")
private final Integer jpaId = null;
private final UUID uuid;
public Component()
{
this.uuid = UUID.fromString(UUIDGenerator.getInstance().generateRandomBasedUUID().toString());
}
@Override
public int hashCode()
{
return this.uuid.hashCode();
}
@Override
public boolean equals(final Object otherObject)
{
if (!(otherObject instanceof Component))
{
return false;
}
final Component otherComponent = (Component) otherObject;
return this.uuid.equals(otherComponent.uuid);
}
}
and its mapping file, Component.hbm.xml:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="Component" table="COMPONENT">
<id name="jpaId" column="JPA_ID" access="field">
<generator class="native" />
</id>
<property name="uuid" column="UUID" access="field" type="serializable" />
</class>
</hibernate-mapping>
The enumeration for theType field, EContainer.java:
Code:
public enum EContainer
{
INT,
COMP;
}
The definition of the class that causes problems, the class that contains theValue (which is here actually a list) and theType (which is here named containerMeta), Container.java:
Code:
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.safehaus.uuid.UUIDGenerator;
public class Container<E>
{
/**
* JPA persistence ID, may or may not be used by the persistence implementation. Not to be used
* by application logic!
*/
@SuppressWarnings("unused")
private final Integer jpaId = null;
private final UUID uuid;
@SuppressWarnings("unused")
private final EContainer containerMeta;
protected final List<E> containedItems;
/**
* default constructor, may or may not be used by the persistence implementation. Not to be used
* by application logic!
*/
@SuppressWarnings("unused")
private Container()
{
this.uuid = UUID.fromString(UUIDGenerator.getInstance().generateRandomBasedUUID().toString());
this.containerMeta = null;
this.containedItems = new ArrayList<E>();
}
public Container(final EContainer containerMeta)
{
this.uuid = UUID.fromString(UUIDGenerator.getInstance().generateRandomBasedUUID().toString());
this.containerMeta = containerMeta;
this.containedItems = new ArrayList<E>();
}
@Override
public int hashCode()
{
return this.uuid.hashCode();
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(final Object otherObject)
{
if (!(otherObject instanceof Container))
{
return false;
}
final Container otherContainer = (Container) otherObject;
return this.uuid.equals(otherContainer.uuid);
}
}
and my attempted mapping file:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<typedef name="EContainer" class="org.hibernate.type.EnumType">
<param name="type">12</param>
<param name="enumClass">EContainer</param>
</typedef>
<class name="Container" table="CONTAINER" abstract="true">
<id name="jpaId" column="JPA_ID" access="field">
<generator class="native" />
</id>
<discriminator column="CONTAINER_META" insert="false"
type="string" />
<property name="uuid" column="UUID" access="field" type="serializable" />
<property name="containerMeta" column="CONTAINER_META"
access="field" type="EContainer" />
<subclass name="Container" discriminator-value="INT"
entity-name="int container">
<list name="containedItems" table="CONTAINER_TO_INTEGER_ASSO"
access="field" cascade="all,delete-orphan">
<key column="CONTAINER" />
<list-index />
<element column="INTEGER" type="integer" />
</list>
</subclass>
<subclass name="Container" discriminator-value="COMP"
entity-name="comp container">
<list name="containedItems" table="CONTAINER_TO_COMPONENT_ASSO"
access="field" cascade="all,delete-orphan">
<key column="CONTAINER" />
<list-index />
<many-to-many column="COMPONENT" class="Component" />
</list>
</subclass>
</class>
</hibernate-mapping>
In my trials this code was used by the following main method:
Code:
import org.hibernate.Session;
public class HibernateMain
{
public static void main(final String[] args)
{
final Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
final Container<Integer> theEvent1 = new Container<Integer>(EContainer.INT);
theEvent1.containedItems.add(new Integer(14));
theEvent1.containedItems.add(new Integer(25));
theEvent1.containedItems.add(new Integer(36));
final Container<Component> theEvent2 = new Container<Component>(EContainer.COMP);
theEvent2.containedItems.add(new Component());
theEvent2.containedItems.add(new Component());
session.save(theEvent1);
session.save(theEvent2);
session.getTransaction().commit();
HibernateUtil.getSessionFactory().close();
}
}
The result of this mapping file is that containedItems is not persisted at all. Probably because hibernate uses only the <class/> definition (even though it is 'abstract') and does not know which <subclass/> definition to use.
Please anybody, if you have an idea on how to solve this problem without changing the data-model, please share. I have tried quite a lot and am now on the point of giving up and make lots of subclasses of Container. I still hope for some still-hidden-to-me bit of Hibernate-magic.
Many thanks in advance for any input you might give!
Regards,
Paul