Version 3.1.3
When a Map field (data member) of a persisted object contains "value" elements, changes made to that map are not persisted, as opposite to the situation when a map containd entities elements (with cascade="all").
I'm not really sure if that it is a bug, but according to my understanding of "value objects" semantics things should work differently.
Lets start with:
Code:
abstract class AbstractObject {
AbstractObject() {}
Integer id;
Map properties;
}
assuming that instance of an AbstractObject has unique id and a collection of properties (implemented as a map keyed by property name).
Here are two implementations of concrete classes (together with corresponding mappings):
Code:
// properties - map of Strings
class ObjectA extends AbstractObject {
ObjectA() {}
}
<class name="ObjectA" table="OBJECT">
<id name="id"
column="ID"
access="field"/>
<map name="properties"
table="PROPERTIES"
lazy="false"
inverse="true"
cascade="all,delete-orphan"
fetch="join"
access="field">
<key column="OBJECT"/>
<map-key column="NAME" type="string"/>
[b]<element column="VALUE" type="string"/>[/b]
</map>
</class>
In ObjectA properties are considered as Map<String, String> while in ObjectB properties will be interpreted as Map<String, Property>:
Code:
// properties - map of Properties
class ObjectB extends AbstractObject {
ObjectB() {}
}
class Property {
Property() {}
static class Id implements Serializable {
Id() {}
Integer object;
String name;
}
Id id;
String value;
}
<class name="ObjectB" table="OBJECT">
<id name="id"
column="ID"
access="field"/>
<map name="properties"
table="PROPERTIES"
lazy="false"
inverse="true"
cascade="all,delete-orphan"
fetch="join"
access="field">
<key column="OBJECT"/>
<map-key column="NAME" type="string"/>
[b]<one-to-many class="Property"/>[/b]
</map>
</class>
<class name="Property" table="PROPERTIES">
<composite-id
name="id"
class="Property$Id"
access="field">
<key-property
name="object"
type="integer"
column="OBJECT"
access="field"
/>
<key-property
name="name"
type="string"
column="NAME"
access="field"
/>
</composite-id>
<property
name="value"
type="string"
column="VALUE"
access="field"
/>
</class>
ObjectA and ObjectB provide two different views for the same table OBJECTS. The main difference here is that ObjectA maps content if the properties as <element column="VALUE" type="string"/> while ObjectB does so by <one-to-many class="Property"/>.
Here is where problems starts. Having:
Code:
Integer objectId = new Integer(1);
String name = "test";
AbstractObject object = null;
Following code:
Code:
object = new ObjectA();
object.id = objectId;
object.properties = new HashMap();
String property = "abc";
object.properties.put(name, property);
session.save(object);
absolutely fails to persist the content of properties, while
Code:
object = new ObjectB();
object.id = objectId;
object.properties = new HashMap();
Property property = new Property();
property.id = new Property.Id();
property.id.object = objectId;
property.id.name = name;
property.value = "abc";
object.properties.put(name, property);
session.save(object);
works just fine.
Attempting to modify persisted object yields similar results:
Code:
object = (AbstractObject)session.get(ObjectA.class, objectId);
object.properties.put(name, "xyz");
session.save(object);
has no effect on the db, when:
Code:
object = (AbstractObject)session.get(ObjectB.class, objectId);
Property property = (Property)object.properties.get(name);
property.value = "xyz";
object.properties.put(name, property);
session.save(object);
does what it is supposed to do.
Here I include complete sources for java code:
Code:
import java.io.*;
import java.util.*;
import org.hibernate.*;
import org.hibernate.cfg.*;
abstract class AbstractObject {
AbstractObject() {}
Integer id;
Map properties;
}
//properties - map of Strings
class ObjectA extends AbstractObject {
ObjectA() {}
}
//properties - map of Properties
class ObjectB extends AbstractObject {
ObjectB() {}
}
class Property {
Property() {}
static class Id implements Serializable {
Id() {}
Integer object;
String name;
}
Id id;
String value;
}
class Test {
static final SessionFactory sessionFactory =
new Configuration().configure().buildSessionFactory();
public static void main(String[] args) throws Exception {
Session session;
Integer objectId = new Integer(1);
String name = "test";
AbstractObject object = null;
Object property = null;
// CREATE
session = sessionFactory.getCurrentSession();
session.beginTransaction();
// creating object as ObjectA will not persist properties !!!
object = new ObjectB();
object.id = objectId;
object.properties = new HashMap();
if (object instanceof ObjectA) {
property = "abc";
}
else
if (object instanceof ObjectB) {
Property p = new Property();
p.id = new Property.Id();
p.id.object = objectId;
p.id.name = name;
p.value = "abc";
property = p;
}
object.properties.put(name, property);
session.save(object);
session.getTransaction().commit();
// UPDATE
session = sessionFactory.getCurrentSession();
session.beginTransaction();
// getting object as ObjectA will not persist changes !!!
object = (AbstractObject)session.get(ObjectB.class, objectId);
property = object.properties.get(name);
System.out.print("old value: ");
if (object instanceof ObjectA) {
System.out.println(property);
property = "xyz";
}
else
if (object instanceof ObjectB) {
System.out.println(((Property)property).value);
((Property)property).value = "xyz";
}
object.properties.put(name, property);
session.save(object);
session.getTransaction().commit();
// SELECT
session = sessionFactory.getCurrentSession();
session.beginTransaction();
// it does not matter if ObjectA or ObjectB is retrieved
object = (AbstractObject)session.get(ObjectB.class, objectId);
property = object.properties.get(name);
System.out.print("new value: ");
if (object instanceof ObjectA) {
System.out.println(property);
}
else
if (object instanceof ObjectB) {
System.out.println(((Property)property).value);
}
session.getTransaction().commit();
}
}
and mappings:
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="ObjectA" table="OBJECT">
<id name="id"
column="ID"
access="field"/>
<map name="properties"
table="PROPERTIES"
lazy="false"
inverse="true"
cascade="all,delete-orphan"
fetch="join"
access="field">
<key column="OBJECT"/>
<map-key column="NAME" type="string"/>
<element column="VALUE" type="string"/>
</map>
</class>
<class name="ObjectB" table="OBJECT">
<id name="id"
column="ID"
access="field"/>
<map name="properties"
table="PROPERTIES"
lazy="false"
inverse="true"
cascade="all,delete-orphan"
fetch="join"
access="field">
<key column="OBJECT"/>
<map-key column="NAME" type="string"/>
<one-to-many class="Property"/>
</map>
</class>
<class name="Property" table="PROPERTIES">
<composite-id
name="id"
class="Property$Id"
access="field">
<key-property
name="object"
type="integer"
column="OBJECT"
access="field"
/>
<key-property
name="name"
type="string"
column="NAME"
access="field"
/>
</composite-id>
<property
name="value"
type="string"
column="VALUE"
access="field"
/>
</class>
</hibernate-mapping>