shelley
shelley

Reputation: 7324

Hibernate WrongClassException for Custom Discriminators

I have a concrete JPA entity superclass mapped with the InheritanceType.JOINED using discriminator columns, and have a couple subclasses entities that extend this superclass with additional properties.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "TYPE")
public class BaseEntity {
    // . . .
}

@Entity
@DiscriminatorValue("SUBTYPE")
public class SubclassEntity extends BaseEntity {
    // . . .
}

There are cases where I want to specify additional discriminator values without having to explicitly define a subclass for every type (that is, not every "BaseEntity" specifies additional properties that warrant a subclass / separate table). This strategy works fine in the database design as well as the Java class hierarchy, however, Hibernate JPA does not allow this and throws a WrongClassException because there isn't a subclass mapped to the discriminator:

Caused by: org.hibernate.WrongClassException: Object [id=entity-1] was not of the specified subclass [com.so.jpa.BaseEntity] : Discriminator: custom-1
    at org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl.getConcreteEntityTypeName(EntityReferenceInitializerImpl.java:415)
    at org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl.hydrateEntityState(EntityReferenceInitializerImpl.java:217)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.readRow(AbstractRowReader.java:90)
    at org.hibernate.loader.plan.exec.internal.EntityLoadQueryDetails$EntityLoaderRowReader.readRow(EntityLoadQueryDetails.java:238)
    at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:112)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:121)
    at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:85)
    at org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167)
    at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3954)
    at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:488)
    at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:453)
    at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:196)
    at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:258)
    at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:134)
    at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1071)
    at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:990)
    at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:632)
    at org.hibernate.type.EntityType.resolve(EntityType.java:424)
    at org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:154)
    at org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:128)
    at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:1132)
    at org.hibernate.loader.Loader.processResultSet(Loader.java:992)
    at org.hibernate.loader.Loader.doQuery(Loader.java:930)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:336)
    at org.hibernate.loader.Loader.doList(Loader.java:2611)
    at org.hibernate.loader.Loader.doList(Loader.java:2594)
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2423)
    at org.hibernate.loader.Loader.list(Loader.java:2418)
    at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:501)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:371)
    at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:220)
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1268)
    at org.hibernate.internal.QueryImpl.list(QueryImpl.java:87)
    at org.hibernate.jpa.internal.QueryImpl.list(QueryImpl.java:567)
    at org.hibernate.jpa.internal.QueryImpl.getResultList(QueryImpl.java:436)
    ...

In this case, I want Hibernate to return the concrete base entity BaseEntity rather than trying to instantiate a subclass. I don't see anything in the JPA spec (JSR 338) that indicates this shouldn't be possible (although the spec doesn't explicitly call out this scenario either).

Is there any way to allow JPA/Hibernate to allow custom discriminator types without requiring subclasses?

Upvotes: 2

Views: 2433

Answers (2)

Vlad Mihalcea
Vlad Mihalcea

Reputation: 153810

There is even a simpler solution to this problem. You could use a @DiscriminatorValue("not null"), like this:

@Entity
@DiscriminatorValue("not null")
public class MiscSubclassEntity extends BaseEntity {
    // . . .
}

This way, whenever a non-mapped discriminator value is found, a MiscSubclassEntity will be used instead. For more on this topic, check this Hibernate blog post.

Upvotes: 0

Tobias Liefke
Tobias Liefke

Reputation: 9022

Unfortunately Hibernate expects exactly one discriminator value per entity type. And I guess that there is no difference to other JPA providers, as you can't define more than one DiscriminatorValue for an entity class.

Even if you define no DiscriminatorValue, there will be exactly one:

If the DiscriminatorValue annotation is not specified and a discriminator column is used, a provider-specific function will be used to generate a value representing the entity type. If the DiscriminatorType is STRING, the discriminator value default is the entity name.

(excerpt from the JavaDoc of DiscriminatorValue)

But you can define a DiscriminatorFormula instead of a DiscriminatorColumn in Hibernate:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorFormula(
    "CASE WHEN TYPE IN ('SUBTYPE', 'SUBTYPE-2', ...) THEN TYPE ELSE 'BaseEntity'")
public class BaseEntity {
    // . . .
}

@Entity
@DiscriminatorValue("SUBTYPE")
public class SubclassEntity extends BaseEntity {
    // ...
}

Disadvantage of that solution: You need to declare the discriminator values of all subtypes in BaseEntity.

Upvotes: 1

Related Questions