Search code examples
javagenericseclipselinkmappedsuperclass

EclipseLink @MappedSuperclass and generics


I have a few domain model classes in my web app that have a hierarchical relationship to themselves. An example of one is the hierarchical category structure used to classify users postings.

There is some logic relating to the hierarchical nature of these classes that is common. So I tried to move the logic into a generic @MappedSuperclass annotated superclass.

Something like :

@MappedSuperclass
public abstract class HierarchicalBaseEntity<N extends HierarchicalBaseEntity<N>>
        extends BaseEntity {

    @ManyToOne(optional=true)
    @JoinColumn(name="parent")
    private N parent;

    private int depth;

    public N getParent() { ...
    public void setParent(N newParent) { ...

    public boolean isRoot() { ...
    public int getDepth() { ...

    public boolean isDescendantOf(N ancestor) { ...
    public static <N extends HierarchicalBaseEntity<N>> N getCommonAncestor(N a, N b) { ...
    public static <N extends HierarchicalBaseEntity<N>> Collection<N> reduceToCommonAncestors(Collection<N> entities) { ...
}

The subclasses then extend HierarchicalBaseEntity giving themselves as the generic type N:

@Entity
public class CategoryBean extends HierarchicalBaseEntity<CategoryBean> {

In Java this all works out quite cleanly. But unfortunately EclipseLink doesn't seems to like the generic 'parent' field:

private N parent;

It gives the following exception:

Caused by: Exception [EclipseLink-7250] (Eclipse Persistence Services - 2.1.0.v20100614-r7608): org.eclipse.persistence.exceptions.ValidationException
Exception Description: [class net.timp.yaase.core.model.HierarchicalBaseEntity] uses a non-entity [class java.lang.String] as target entity in the relationship attribute [field parent].
at org.eclipse.persistence.exceptions.ValidationException.nonEntityTargetInRelationship(ValidationException.java:1341)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.RelationshipAccessor.getReferenceDescriptor(RelationshipAccessor.java:416)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOneToOneForeignKeyRelationship(ObjectAccessor.java:609)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOwningMappingKeys(ObjectAccessor.java:678)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ManyToOneAccessor.process(ManyToOneAccessor.java:107)

Why is it complain about a non-entity String?

As a test I've tried removing the generics and just having the parent field defined as:

private HierarchicalBaseEntity parent;

Without generics, EclipseLink gave this exception:

Caused by: Exception [EclipseLink-7250] (Eclipse Persistence Services - 2.1.0.v20100614-r7608): org.eclipse.persistence.exceptions.ValidationException
Exception Description: [class net.timp.yaase.core.model.OnymBean] uses a non-entity [class net.timp.yaase.core.model.HierarchicalBaseEntity] as target entity in the relationship attribute [field parent].
at org.eclipse.persistence.exceptions.ValidationException.nonEntityTargetInRelationship(ValidationException.java:1341)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.RelationshipAccessor.getReferenceDescriptor(RelationshipAccessor.java:416)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOneToOneForeignKeyRelationship(ObjectAccessor.java:609)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOwningMappingKeys(ObjectAccessor.java:678)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ManyToOneAccessor.process(ManyToOneAccessor.java:107)

True HierarchicalBaseEntity its not an Entity in either case, is a @MappedSuperclass.. but is there a way to do this with generics or otherwise? It seems you can't have a field in your @MappedSuperclass that references one of it's subclass.


Solution

  • The issue is that when using Generics as field types for relationships EclipseLink can not know what the target type is until runtime when the actual instance is inspected. So the mapping would have to be dynamically created at runtime and this is not supported.

    You could continue to use the Generic SuperClass but this would require moving the field to the Entities where they would have types defined then have abstract internal getters/setters for those fields that return Object that the generic methods would call casting to the generic type. Convoluted but it would allow for the Generic MappedSuperclass.