Hibernate WrongClassException for Custom Discriminators - java

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?

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.

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.

Related

How to make Spring (Hibernate) instantiate child entities in single table inheritance scenario as their proxies instead of base entity proxy?

Recently I started getting Hibernate's HHH000179: Narrowing proxy to class warning when attempting to delete child entites (inheritance) that contain other relations.
This led me to learning that Hibernate proxies are created for each entity separately, even if such entity is an abstract base entity (not #MappedSuperclass, but just abstract #Entity) - that is even if base entity class will never exist on its own.
Consider structure of attributes:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(...)
public abstract class Attribute { ...ids and common fields... }
#Entity
#DiscriminatorValue("V")
public class AttributeValued extends Attribute
{
#OneToMany( mappedBy = "attribute",
cascade = CascadeType.ALL,
orphanRemoval = true)
private Set<Value> values = new LinkedHashSet<>();
}
With Spring JPA i have repository such as:
interface AttributeRepository extends JpaRepository<Attribute, Long> {}
Consider given some id that is known to be of type AttributeValued I run this:
Attribute a = this.attributeRepository.getReferenceById(id);
if (a instanceof AttributeValued)
{
System.out.println("VALUED");
}
else
{
System.out.println("OTHER");
}
This prints 'OTHER' while I'd expect it to print VALUED.
That means that Spring/Hibernate instantiated #getReferenceById to base class Attribute (or rather it's Hibernate proxy).
Is there a way for Spring/Hibernate to return proxy of actual entity (AttributeValued) when using common JpaRepository<Attribute>?
Because of this behivior if I do something like this:
// id is known to be of type AttributeValued
Attribute a = this.attributeRepository.getReferenceById(id);
this.attributeRepository.delete(a);
Hibernate will strike me with HHH000179: Narrowing proxy to class AttributeValued because variable a is proxy of Attribute while delete(a) will create another representation of the same row as proxy of AttributeValued, because of AttributeValued.values relation having:
cascade = CascadeType.ALL
orphanRemoval = true
So now I have proxy of Attribute and proxy of AttributeValued (I think).
You can use #Embedded annotation on Child entity so that child and parent entities will form a single table in database
You can read more about this in official documentation https://www.baeldung.com/jpa-embedded-embeddable
Okay, so problem was absolutely elsewhere than I thought.
#getReferenceById is JpaRepository method.
which I used basically everywhere in my whole project (since Hibernate is Jpa and I assumed it's the correct way).
Turns out that querying with it is for some reason not polymorphic and returns proxy of Attribute, even if row is known to represent AttributeValued (with #DiscriminatorValue("V")).
#findById(id) is a CrudRepository method and it actually is polymorphic and returns proxy of AttributeValued, thus when I use #delete(id) later, I will not get problems like in original question (two proxies for the same row and warning HHH000179: Narrowing proxy to class).

Hibernate mapping annotation skipped by the system

I have an issue with the mapping under hibernate. I have an abstract class which implements an interface:
#MappedSuperclass
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractPromotion implements IPromotion {
}
This abstract class is the super class(as you can see with the annotation) and 3 mapped classes with #Entity extend this super class. When i launch a "maven install" i get this failure message inside the console , it says :
Caused by: org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class: product.model.Product.promotion[product.promotion.AbstractPromotion]
i don't understand it because the class in question is mapped (as the classes which extend it) as above but it's like the annotations are skipped...
any help would be really appreciated
best regards
AbstractPromotion is not an entity. Its a MappedSuperclass. To apply an inheritance strategy, and to be the target of an association, a class must be an entity. Replace #MappedSuperclass by #Entity.
Mappedsuperclass is only used to be able to inherit common fields and methods in several, unrelated entities (like, for example, inherit an id, or a creationDate, in several unrelated entities).
If you want share the same database table for all the sublcass you need use discriminators.
In the main class you need to add this annotations.
#Entity
#Table(name = "foo")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "foo_type", discriminatorType = DiscriminatorType.STRING
And in the subclass this annotation
#Entity
#DiscriminatorValue("The value of your foo_type that discriminate this entity")

Any way to get fields from entity without actually joining into that table?

I'm not sure this is possible, but knowing just the very basics of JPA, I want to ask if it is possible. Basically I have an entity (We'll call it MyEntity) with a bunch of fields on it. We now want a 2nd entity that has all the same fields as MyEntity plus some of it's own. The use case for this is archiving these entities. We want to store all the archived entities in a separate table than MyEntity so that we don't have to qualify all the queries with archived=false. The JPA annotations for MyEntity look something like this:
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="TYPE")
public abstract class MyEntity
{
....
There are multiple classes that extend this abstact class, each with #DiscriminatorValue annotations
For my archived entity (MyArchivedEntity) I want something along the lines of this:
#Entity
public class MyArchivedEntity
{
private MyEntity entity;
private String archiveSpecificField;
....
The problem with this of course is that it will want to join into the MyEntity table and get a specifc MyEntity record for populate the entity field. Is there some kind of annotation or something I can do to just get the same fields/columns from that entity (MyEntity) into this entity (MyArchivedEntity)?
Like I said in the beginning, I'm not sure if this is possible, but I hope I've explained well enough the end goal of what I'm trying to achieve, so that there could be some way to achieve it. If it makes any difference, I'm using PostgreSQL with EclipseLink.
What you can do is using #MappedSuperclass on a AbstractParentEntity becoming the super class of both MyEntity and MyArchiveEntity. So you will have something like the following:
#MappedSuperclass
public abstract class AbstractParentEntity {
public String someField;
...
}
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="TYPE")
public abstract class MyEntity extends AbstractParentEntity
{
//here you don't have any field (they all move to AbstractParentEntity
// (or, at least all the fields that need to be archivied are now declared in parent class)
....
}
#Entity
public class MyArchivedEntity extends AbstractParentEntity
{
private String archiveSpecificField;
....
More about MappedSuperclass here:
Mapped superclass inheritance allows inheritance to be used in the object model, when it does not exist in the data model. It is similar to table per class inheritance, but does not allow querying, persisting, or relationships to the superclass. Its' main purpose is to allow mappings information to be inherited by its' subclasses. The subclasses are responsible for defining the table, id and other information, and can modify any of the inherited mappings. A common usage of a mapped superclass is to define a common PersistentObject for your application to define common behavoir and mappings such as the id and version. A mapped superclass normally should be an abstract class. A mapped superclass is not an Entity but is instead defined though the #MappedSuperclass annotation or the <mapped-superclass> element.
You may wish to look into EclipseLink's history support. It can automatically maintain a historical archive table.
See,
http://wiki.eclipse.org/EclipseLink/Examples/JPA/History
Another option would be to map the same classes in another persistence unit using an orm.xml to the archive tables.

Single Table Inheritance WITHOUT Discriminator column

Good Morning, my Dear comrades,
This is starting to be come annoying - a simple thing, but hours of struggle, am I getting old??
I am trying to map two Classes to a single table using JPA by Hibernate. The idea is to have only a small subset of columns in parent Class, and bigger/full set in the child Class. There is NO TABLE inheritance involved, only class inheritance.
How can this be accomplished??
Doing this will not work:
#Entity
#Table(name = "the_table")
class Parent implements Serializable {
}
#Entity
#Table(name = "the_table")
class Child extends Parent implements Serializable {
}
Hibernate assumes default inheritance strategy InheritanceType.SINGLE_TABLE, and is looking for discriminator column - DTYPE by default. But wait - there is no table inheritance, having the discriminator column does not make sence.
I have also taken a look at PolymorphismType.EXPLICIT which did not make any difference. The stack trace is:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'apprentice0_.DTYPE' in 'where clause'
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
at com.mysql.jdbc.Util.getInstance(Util.java:386)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1052)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3597)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3529)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1990)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2151)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2625)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2119)
at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:2281)
at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeQuery(NewProxyPreparedStatement.java:76)
at org.hibernate.jdbc.AbstractBatcher.getResultSet(AbstractBatcher.java:208)
at org.hibernate.loader.Loader.getResultSet(Loader.java:1808)
at org.hibernate.loader.Loader.doQuery(Loader.java:697)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
at org.hibernate.loader.Loader.loadEntity(Loader.java:1881)
yeah, one more thing:
The #MappedSuperclass and #Embeddable are of no use as these can not be used in conjunction with #Entity - the parent class has to be an #Entity itself as it is being used for persistence elsewhere.
#MappedSuperclass is the annotation that must be used. If the same class is both a mapped super class and an entity, then simply split it into two classes:
#MappedSuperclass
public class Parent {
// ...
}
#Entity
public class ParentEntity extends Parent {
// no code at all here
}
#Entity
public class Child extends Parent {
// additional fields and methods here
}
There are a couple of ways each with their own caveats.
1) Add annotations as following:
#DiscriminatorFormula("0")
#DiscriminatorValue("0")
class BaseClass{ }
#DiscriminatorValue("00")
class SubClass extends BaseClass{ }
where the subclasses discriminator value must be different to the base class' but also evaluate to the same value when passed into an Integer.valueOf(String s) method.
The caveat - if you return an object from Hibernate of the base class and then again when calling for the subclass type you will get an error complaining the loaded object was of the wrong class. If you call the subclass query first however the base class call will return the subclass.
2) Use a view in the database to map the table and use this as the table of the sub class. In fact it can be any other class that matches the column mappings as Hibernate thinks its a completely separate table.
Caveat - You will potentially have the same row instantiated as two different objects that will not be synchronised and could lead to conflicting/lost database updates.
It's probably better to stick with one type for a session and that could be handled without the runtime risks by using an entity mapping xml file that overrides the DiscriminatorValue of the desired class to match the constant Discriminator'Formula' value which you can pass into the initial configuration.
Make a view of the table with the limited set of columns and map the second class to that one. Define an interface with the limited set of columns and have both class implement the interface. That probably gets you about 95% of what you need. If you need to, create methods to define equality between the two as well as being able to convert the larger class (via a constructor?) to the smaller class.
You have to select an inheritance type for two entities.
What you are trying to do is not appropriate, because hibernate won't know what objects to instantiate.
If you simply need an object to have fewer fields, then don't map it as an entity - just provide a constructor that copies all fields from the other class.
If you don't want to add auto dtype column, you should define your own Discriminator by #DiscriminatorFormula
#Entity
#Table(name = "CS_CUSTOMER")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorFormula("case when id < 3 then 'VIP' else 'Customer' end")
public class Customer extends BaseEntity {
...
#Entity
public class VIP extends Customer {
...
The Discriminator name is default to Entity class name, if you want to change it, use
#DiscriminatorValue("VIP")
Log from hibernate
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate:
create table cs_customer (
id bigint not null,
create_user_id bigint,
first_name varchar(255),
last_name varchar(255),
primary key (id)
)

EclipseLink/JPA: Multiple #DiscriminatorColumn annotations for Entity

I'm looking for a way in EclipseLink to have two #DiscriminatorColumns on the same entity
My PostreSQL DB table is:
Dictionary
{
id,
object_type,
attribute_type,
translation
}
And classes are:
#Entity
#Table(name = "dictionary")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="object_type",
discriminatorType=DiscriminatorType.INTEGER)
public class DictionaryRow implements Serializable;
#Entity
#DiscriminatorValue("0")
#DiscriminatorColumn(name="info_type",
discriminatorType=DiscriminatorType.INTEGER)
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class DictionaryAttribute extends DictionaryRow;
#Entity
#DiscriminatorValue("1")
public class DictionaryAttributeName extends DictionaryAttribute;
What I'm trying to achieve is that when I call for DictionaryAttributeName it will be resolved to SQL like:
select * from DICTIONARY where info_type = 1 and object_type = 0
But actually, it takes the DiscriminatorColumn from the DictionaryRow class, and DiscriminatorValue from the DictionaryAttributeName, resulting in the totally wrong SQL:
select * from DICTIONARY where object_type = 1
Is there a solution for this issue?
Thanks
According to the JPA 2.0 specification, this is not possible:
11.1.10 DiscriminatorColumn Annotation
For the SINGLE_TABLE mapping
strategy, and typically also for the
JOINED strategy, the persistence
provider will use a type discriminator
column. The DiscriminatorColumn
annotation is used to define the
discriminator column for the
SINGLE_TABLE and JOINED
inheritance mapping strategies.
The strategy and the discriminator column are only specified in the root
of an entity class hierarchy or
subhierarchy in which a different
inheritance strategy is applied.
References
JPA 2.0 Specification
Section 11.1.10 "DiscriminatorColumn Annotation"

Categories

Resources