Hibernate upgrade object from parent class to child class in JOINED inheritance - java

I'm using Spring Data Jpa and Hibernate on my project.
I have three tables:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
class Parent {
String id;
String name;
}
#Entity
class FirstChild extends Parent {
...
}
#Entity
class SecondChild extends Parent {
...
}
On the first step of my logic I should save Parent object without child type.
And on the second step I know to which Child table it should belong.
For example:
Parent parent = parentRepository.findById("id");
FirstChild firstChild = new FirstChild();
firstChild.setId(parent.getId());
firstChild.setName(parent.getName());
parentRepository.save(firstChild);
But when I do a Hibernate save it throws me exception:
o.h.e.i.DefaultLoadEventListener Load request found matching entity in context, but the matched entity was of an inconsistent return type; returning null
As I understand it doesn't know how to upgrade entity from parent to child type and just throws an exception because of conflict - entity with same id is already there.
Is there any solutions for this problem?

JPA is a means of mapping your Java domain model onto a relational database schema. Since there is no such thing as 'promoting a parent class to a child class' in Java, there is no support in JPA for such an operation.
That being said, you could probably achieve the desired behavior using a native update query. You would need to update the discriminator column (DTYPE) column, and insert a new row into the table corresponding to the child entity (note that in the SINGLE_TABLE strategy, updating the discriminator column would suffice).
A much better solution IMHO, is to delete the parent entity and insert a new child entity. If you're concerned about referential integrity, perhaps you should switch from inheritance to composition.

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).

Spring Data JDBC invert OneToOne navigation

I have an existing data scheme I'm reluctant to change. There are two entities/tables: parent and child, with parent having the foreign key column child_id. It's a 1-to-1 relationship.
The problem is: the magic behind the scenes expects the child table to have the foreign key column (the exception mentions a ...JOIN ON child.parent = parent.id). Is it possible to inverse this to match the existing scheme? (I know it is with hibernate, but I'd like to stay with JDBC).
Relevant code:
#Repository
public interface ParentRepository extends CrudRepository<Parent, Long>{
}
#Data
public class Parent {
#Id
private Long id;
private Child child;
}
#Data
public class Child {
#Id
private Long id;
}
Somewhat related question: Spring Data JDBC invert OneToMany navigation
There is currently no support for this on the Spring Data JDBC side.
On option that comes to mind is to create a view that already performs the join and has instead of triggers to perform the correct actions on the Child table.
You can then map the Child as embedded.

Proper way to update a Set element in a JPA #OneToMany relationship?

Lets assume we have a bi-directional One-to-Many relationship between Parent and Child.
I like the idea of model that relationship with a Set, because of it intrinsic nature of disallowing duplicates.
Question:
1) What would be the proper JPA way to update a child in such a situation?
Query the Parent and pass an updated Child into it?
Query the Child directly and just call its setters?
2) Has either way some performance advantages or disadvantages?
#Entity
public class Parent extends AbstractPersistable<Long> {
#OneToMany(cascade = CascadeType.ALL, ... )
private Set<Child> children = new HashSet();
public void addChild( Child child ) { ... }
public void removeChild( Child child ) { ... }
// non-anemic domain model ?
public void updateChild( Child child ) {
// how to update the element in the Set?
}
}
UPDATE:
How to properly write the update method? Since Sets in Java do not have a get method?
To update a Child, you don't need to operate the parent collection.
Thanks to the dirty checking mechanism, once the Child becomes managed in the currently running Persistence Context, every change is picked automatically and synchronized to the database.
That's the reason you don't have an update method in JPA. You only have persist or merge in EntityManager.
So, you need to do the following steps:
You load the Child by id:
Child child = entityManager.find(Child.class, childId);
Do the changes on the Child and you are done:
child.setName(newName);

Inheritance on Hibernate objects without table mapping

I want to use hibernate objects in project as defined below.
#Table(name = "Parent")
class Parent{
int id;
String name;
}
#Table(name = "Child")
class Child extends Parent{
String schoolNo;
}
But in the database;
There is no relation with these two table.
Parent tables columns are; id, name
Child tables columns are; id, name and schoolNo
If I use
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
when I send a query for Parent object, hibernate use UNION on Child and Parent tables but I want to select from only Parent table.
And if I use
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
hibernate wants a discriminator column.
I need hibernate sends select query for each class to its table.
Best regards.
TABLE_PER_CLASS is the correct strategy here.
It's odd that Hibernate generates a union query over both tables, but that should still work. The subquery over the wrong table won't find anything, so the results will be correct. This sounds like a bug in Hibernate's query generation for subclasses.
In a similar situation, I use #Inheritance(strategy = InheritanceType.JOINED) on the parent table.
See more info in the Hibernate docs: http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#d0e1168

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)
)

Categories

Resources