Markus
Markus

Reputation: 2053

How to map an attribute like Map<Class, AbstractEntity> with hibernate?

Is there any possibility with Hibernate to do the following entity structure?

@Entity
public class Person {

    @OneToMany
    private Map<Class<? extends PersonRole>, PersonRole> personRoles;

    public <T extends PersonRole> T getRole(Class<T> roleClass) {
         return roleClass.cast(roles.get(roleClass));
    }

}

@Entity
public abstract class PersonRole {

    @ManyToOne
    private Person person;

}

Basically Hibernate can persist this mapped entity but it is not possible to load it anymore from the database with the following exception:

Exception in thread "main" org.hibernate.HibernateException: null index column for      collection: de.his.cs.sys.hibernate.Person.roles
at org.hibernate.persister.collection.AbstractCollectionPersister.readIndex(AbstractCollectionPersister.java:822)
at org.hibernate.collection.internal.PersistentMap.readFrom(PersistentMap.java:277)
at org.hibernate.loader.Loader.readCollectionElement(Loader.java:1189)
at org.hibernate.loader.Loader.readCollectionElements(Loader.java:804)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:655)
at org.hibernate.loader.Loader.doQuery(Loader.java:854)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:293)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:263)
at org.hibernate.loader.Loader.loadCollection(Loader.java:2094)
at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:61)

A workaround could be using a "simple" collection and filling the map with an interceptor, but I hope for a possibility achieving this without additional infrastructure.

Upvotes: 1

Views: 280

Answers (2)

Markus
Markus

Reputation: 2053

The problem basically seems to me, that hibernate needs to rely on a persistent attribute for a map key. Therefore the solution adds a new attribute to the abstract class RersonRole:

private Class<?> className = this.getClass();

Then it is possible to refer to it in the @MapKey annotation in the class Person:

@OneToMany
@MapKey(name = "className")
private Map<Class<? extends PersonRole>, PersonRole> personRoles;

With this mapping hibernate can now fill the Map without further infrastructure.

This from my point of view mostly elegant solution has the drawback of adding a persistent attribute, which is only needed because of hibernate (If I get the root cause of the problem right).

Upvotes: 0

Firo
Firo

Reputation: 30813

it is possible implementingh a Hibernate UserType which maps the class to a string and back

@OneToMany
@MapKey(name = "className" type=@Type(type="namespace.classToNameUserType"))
private Map<Class<? extends PersonRole>, PersonRole> personRoles;

see here for an example UserType

Upvotes: 1

Related Questions