Hi,
I am trying to use hibernate to map a Map of Sets value type. In order to do that I used the UserCollectionType described in
http://blog.xebia.com/2007/10/05/mapping-multimaps-with-hibernate/
The 2 classes are as follows:
MultiMapType.javaCode:
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.collections.MultiHashMap;
import org.apache.commons.collections.MultiMap;
import org.hibernate.HibernateException;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.usertype.UserCollectionType;
public class MultiMapType implements UserCollectionType {
@Override
public boolean contains(Object collection, Object entity) {
return ((MultiMap) collection).containsValue(entity);
}
@Override
public Iterator getElementsIterator(Object collection) {
return ((MultiMap) collection).values().iterator();
}
@Override
public Object indexOf(Object collection, Object entity) {
for (Iterator i = ((MultiMap) collection).entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
Collection value = (Collection) entry.getValue();
if (value.contains(entity)) {
return entry.getKey();
}
}
return null;
}
public Object instantiate() {
return new MultiHashMap();
}
@Override
public PersistentCollection instantiate(SessionImplementor session, CollectionPersister persister) throws HibernateException {
return new PersistentMultiMap(session);
}
@Override
public PersistentCollection wrap(SessionImplementor session, Object collection) {
return new PersistentMultiMap(session, (MultiMap) collection);
}
@Override
public Object replaceElements(Object original, Object target, CollectionPersister persister, Object owner, Map copyCache, SessionImplementor session) throws HibernateException {
MultiMap result = (MultiMap) target;
result.clear();
Iterator iter = ((java.util.Map) original).entrySet().iterator();
while (iter.hasNext()) {
java.util.Map.Entry me = (java.util.Map.Entry) iter.next();
Object key = persister.getIndexType().replace(me.getKey(), null, session, owner, copyCache);
Collection collection = (Collection) me.getValue();
for (Iterator iterator = collection.iterator(); iterator.hasNext();) {
Object value = persister.getElementType().replace(iterator.next(), null, session, owner, copyCache);
result.put(key, value);
}
}
return result;
}
@Override
public Object instantiate(int i) {
if (i == -1) {
return new MultiHashMap();
}
return new MultiHashMap(i);
}
}
,
PersistentMultiMap.java Code:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.commons.collections.DefaultMapEntry;
import org.apache.commons.collections.MultiHashMap;
import org.apache.commons.collections.MultiMap;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.collection.PersistentMap;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.type.Type;
public class PersistentMultiMap extends PersistentMap implements MultiMap {
public PersistentMultiMap(SessionImplementor session, MultiMap map) {
super(session, map);
}
public PersistentMultiMap(SessionImplementor session) {
super(session);
}
@Override
public Object remove(Object key, Object item) {
Object old = isPutQueueEnabled() ? readElementByIndex(key) : UNKNOWN;
if (old == UNKNOWN) {
write();
return ((MultiMap) map).remove(key, item);
} else {
queueOperation(new RemoveItem(key, item));
return old;
}
}
private class RemoveItem implements DelayedOperation {
private final Object key;
private final Object item;
private RemoveItem(Object key, Object item) {
this.key = key;
this.item = item;
}
@Override
public Object getAddedInstance() {
return null;
}
@Override
public Object getOrphan() {
return item;
}
@Override
public void operate() {
((MultiMap) map).remove(key, item);
}
}
@Override
public Iterator entries(CollectionPersister persister) {
return new KeyValueCollectionIterator(super.entries(persister));
}
private final static class KeyValueCollectionIterator implements Iterator {
private final Iterator parent;
private Object key;
private Iterator current;
private KeyValueCollectionIterator(Iterator parent) {
this.parent = parent;
move();
}
@Override
public boolean hasNext() {
return key != null;
}
@Override
public Object next() {
if (key == null) {
throw new NoSuchElementException();
} else {
DefaultMapEntry result = new DefaultMapEntry(key, current.next());
if (!current.hasNext()) {
move();
}
return result;
}
}
private void move() {
while (this.parent.hasNext()) {
Map.Entry entry = (Entry) this.parent.next();
key = entry.getKey();
current = ((Collection) entry.getValue()).iterator();
if (current.hasNext()) {
return;
}
}
key = null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
@Override
public Serializable getSnapshot(CollectionPersister persister) throws HibernateException {
EntityMode entityMode = getSession().getEntityMode();
MultiHashMap clonedMap = new MultiHashMap(map.size());
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry e = (Map.Entry) iter.next();
Collection collection = (Collection) e.getValue();
for (Iterator i = collection.iterator(); i.hasNext();) {
final Object copy = persister.getElementType().deepCopy(i.next(), entityMode, persister.getFactory());
clonedMap.put(e.getKey(), copy);
}
}
return clonedMap;
}
@Override
public boolean equalsSnapshot(CollectionPersister persister) throws HibernateException {
Map sn = (Map) getSnapshot();
if (sn.size() != map.size()) {
return false;
}
Type elemType = persister.getElementType();
for (Iterator i = sn.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
Map oldState = getCollectionAsIdentityMap((Collection) entry.getValue());
Collection newState = (Collection) map.get(entry.getKey());
for (Iterator iter = newState.iterator(); iter.hasNext();) {
Object newValue = iter.next();
Object oldValue = oldState.get(newValue);
if (newValue != null && oldValue != null && elemType.isDirty(oldValue, newValue, getSession())) {
return false;
}
}
}
return true;
}
private Map getCollectionAsIdentityMap(Collection collection) {
Map mp = new HashMap();
for (Iterator iter = collection.iterator(); iter.hasNext();) {
Object element = iter.next();
mp.put(element, element);
}
return mp;
}
@Override
public Iterator getDeletes(CollectionPersister persister, boolean indexIsFormula) throws HibernateException {
Set result = new HashSet();
Map sn = (Map) getSnapshot();
for (Iterator i = sn.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Entry) i.next();
Collection oldState = (Collection) entry.getValue();
Collection newState = (Collection) map.get(entry.getKey());
for (Iterator j = oldState.iterator(); j.hasNext();) {
Object element = j.next();
if (!(newState.contains(element))) {
result.add(element);
}
}
}
return result.iterator();
}
@Override
public boolean needsInserting(Object entry, int i, Type elemType) throws HibernateException {
Map.Entry e = (Entry) entry;
Map sn = (Map) getSnapshot();
Collection oldState = (Collection) sn.get(e.getKey());
return oldState == null || !oldState.contains(e.getValue());
}
@Override
public boolean needsUpdating(Object entry, int i, Type elemType) throws HibernateException {
Map.Entry e = (Entry) entry;
Map sn = (Map) getSnapshot();
Collection collection = (Collection) sn.get(e.getKey());
if (collection == null) {
return false;
}
for (Iterator iter = collection.iterator(); iter.hasNext();) {
Object oldValue = iter.next();
if (oldValue != null && oldValue.equals(e.getValue())) {
return e.getValue() != null && elemType.isDirty(oldValue, e.getValue(), getSession());
}
}
return false;
}
}
and the mapping is:
Code:
<map name="testMap" table="tbl_test" collection-type="MultiMapType" >
<key column="fk_person_id"/>
<composite-map-key class="TestKey">
<key-property name="languageId" type="int" column="fk_language_id"></key-property>
<key-property name="roleId" type="int" column="fk_role_id"></key-property>
</composite-map-key>
<composite-element class="TestValue">
<property column="fld_level" name="level" not-null="true"/>
<property column="fld_info" name="info" not-null="true"/>
</composite-element>
</map>
The 2 classes TestKey and TestValue are:
TestKey.java Code:
public class TestKey implements java.io.Serializable {
private int languageId;
private int roleId;
public TestKey() {
}
public TestKey(int languageId, int roleId) {
this.languageId = languageId;
this.roleId = roleId;
}
@Override
public int hashCode() {
int hash = 7;
hash = 89 * hash + this.languageId;
hash = 89 * hash + this.roleId;
return hash;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TestKey)) {
return false;
}
final TestKey val = (TestKey) o;
if (this.getLanguageId() != val.getLanguageId()) {
return false;
}
return (this.getRoleId() == val.getRoleId());
}
@Override
public String toString() {
return "language: '" + getLanguageId() + "', "
+ "role: '" + getRoleId() + "'";
}
public int getLanguageId() {
return languageId;
}
public void setLanguageId(int languageId) {
this.languageId = languageId;
}
public int getRoleId() {
return roleId;
}
public void setRoleId(int roleId) {
this.roleId = roleId;
}
}
TestValue.java Code:
public class TestValue implements java.io.Serializable {
private int level;
private int info;
public TestValue() {
}
public TestValue(int level, int info) {
this.level = level;
this.info = info;
}
@Override
public int hashCode() {
int hash = 7;
hash = 89 * hash + this.level;
hash = 89 * hash + this.info;
return hash;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TestValue)) {
return false;
}
final TestValue val = (TestValue) o;
if (this.getLevel() != val.getLevel()) {
return false;
}
return (this.getInfo() == val.getInfo());
}
@Override
public String toString() {
return "level: '" + getLevel() + "', "
+ "info: '" + getInfo() + "'";
}
public int getInfo() {
return info;
}
public void setInfo(int info) {
this.info = info;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
}
Finally, I add the org.apache.commons.collections.MultiMap property testMap in the containing entity class (Person.java) with the
appropriate getter/setter methods .
Under this setting I can retrieve the database values in testMap and add new key-value pairs in a hibernate session the following way:
Code:
TestKey key = new TestKey(1,1);
TestValue value = new TestValue(9,9);
user.getTestMap().put(key, value);
The first problem is that when I try to add a second value to the same key, for example
Code:
TestValue value1 = new TestValue(10,10);
user.getTestMap().put(key, value1);
nothing happens.
Also, when I try to clear the map
Code:
user.getTestMap().clear()
or delete a key
Code:
user.getTestMap().remove(key)
I get the following error
Code:
Exception in thread "main" java.lang.NullPointerException
at PersistentMultiMap.getDeletes(PersistentMultiMap.java:185)
at org.hibernate.persister.collection.AbstractCollectionPersister.deleteRows(AbstractCollectionPersister.java:1214)
at org.hibernate.action.CollectionUpdateAction.execute(CollectionUpdateAction.java:54)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:248)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:232)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:142)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
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)
Hope someone could help
Thanks in advance,
lgs