Reputation: 7324
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
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
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 theDiscriminatorType
isSTRING
, 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