Implementing model inheritance on springboot have strange data repository behavior - java

I'm going to create a model that implement inheritance concept. I was thinking that it is possible by creating two (or more) tables (one for the parent class and the other for the child class), then create two (or more) model based on the table.
I currently created a model that acts as a parent class, and made it abstract
#NoArgsConstructor // lombok
#AllArgsConstructor // lombok
#Data // lombok
#Entity(name="Account")
#Inheritance
public abstract class AccountModel {
#Id
#Column(name="username")
private String username;
// Some other fields and getters and setters here
}
and then created child class that extends above class
#NoArgsConstructor // lombok
#Data // lombok
#EqualsAndHashCode(callSuper=true) // lombok
#Entity(name="Administrator")
public class AdministratorModel extends AccountModel {
#Id
#Column(name="username")
private String username;
// some other fields and getters and setters here
}
(currently, the username is used as the join)
and I created two repositories for both models
#Repository
public interface AccountRepository extends JpaRepository<AccountModel, String>{};
#Repository
public interface AdministratorRepository extends JpaRepository<AdministratorModel, String>{};
Then, I tested it by trying to save new AdministratorModel object to AdministratorRepository by using JpaRepository.save() method.
I was expecting that this setting will fill data from the object to both of the tables on the database, and automatically mapped all properties to each table. but, the data is only saved as one record on the parent table, and adding new column (one column is something that may refer to child table, and other column is properties of the child table, without adding the join column 'username'), while leaving the child table empty.
I think that I'm doing it wrong.
Then, how to make it works as expected ?
Should I not used inheritance and save the model manually using two (or more) repositories, or should I only create one repository with a custom query, or is there any other way ?

This kind of inheritance should add non default strategy to #Inheritance annotation.
Changing #Inheritance to #Inheritance(strategy=InheritanceType.JOINED) fixes the problem.
As the documentation says this strategy is:
A strategy in which fields that are specific to a
subclass are mapped to a separate table than the fields
that are common to the parent class, and a join is
performed to instantiate the subclass.

Related

How to handle Runtime Polymorphism in Spring Boot?

I have abstract class Employee
public abstract class Employee {
private int employeeId;
private String name;
}
I also have two concrete classes that extends Employee and that is OfficeEmployee and HomeEmployee which are currently empty.
This my controller:
#RestController
#RequestMapping("/api/employee")
public class EmployeeController {
#Autowired
private EmployeeService employeeService;
#PostMapping("/office")
public EmployeeResponse saveOfficeEmployee(#RequestBody OfficeEmployee request) {
return employeeService.save(request);
}
#PostMapping("/home")
public EmployeeResponse saveHomeEmployee(#RequestBody HomeEmployee request) {
return employeeService.save(request);
}
}
And finally EmployeeService class:
#Service
public class EmployeeService {
#Autowired
private EmployeeRepository employeeRepository;
public Employee save(Employee request) {
// here i think i should do something like this: Employee employee = new OfficeEmployee or
// Employee employee = new HomeEmployee();
Employee employee = employeeRepository.save(employee);
return employee;
}
}
How to determine what employee did i get from POST request? Am i approaching this problem wrong?
Why would you think you need to determine anything? EmployeeRepository is already well-equipped to handle all types of Employees.
For this to work, Employee must be an #Entity. It can still be abstract, though.
As a side note, an alternative to having separate endpoints (/home, /office) is to use #JsonTypeInfo with one of the available strategies to determine Employee subtype from the input data.
Yeah abstract class entity is something I use for adding a common column in multiple tables.
For example if I want to add createdDate and updatedDate on many tables, I would defined those 2 columns in an abstract entity class (call it BaseDateEntity for example), and inherit it in all the entity classes I want to use it. Also annotate the base entity class with #MappedSuperclass. But repositories should be per specific entity classes. You can't use 1 repository for all entities that inherit your abstract Employee entity, otherwise the query will be executed on all subclass entities(OfficeEmployee, HomeEmployee, XyzEmployee, ..) of your superclass baseentity(=Employee), which could be sufficient from time to time.
Rough example of your entity codes.
import java.persistence.MappedSuperclass;
#MappedSuperclass
public abstract class Employee { //body skipped for brevity}
There's an alternative. Use #Entity and #DiscriminatorColumn on your base entity.
And use #Entity & #DiscriminatorValue on your child entity.
#Entity
#DiscriminatorColumn
#Inheritance(strategy=InheritanceType.JOINED) //more explanation on this below
public abstract class Employee {//body skipped for brevity}
#Entity
#DiscriminatorValue("Officeemployee")
public class OfficeEmplyee extends Employee {}
You cannnot use both MappedSuperclass and Entity. So choose one.
Rough example of your repositories
public interface EmployeeRepository<T extends Employee> extends JpaRepository<T, Long>{}
public interface OfficeEmployeeRepository extends JpaRepository<OfficeEmployee,Long>{}
Obviously I skipped code examples for HomeEmployee because that will be same as OfficeEmployee.
Also you don't need OfficeEmployeeRepository if you never want query specifically on OfficeEmployees only. If you always query on all subclasses of Employees, then you only need EmployeeRepository. However in this case I think you need both EmployeeRepository for general Employee query and also OfficeRepository & HomeRepository for query on specific type of employees
To explain a bit further about the difference between MappedSuperclass methodology and DiscriminatorValue methodology, you have to think about tables in DB.
In simple cases where you don't want to create another table for parent (abstract) entity object, it's much simpler to use MappedSuperclass. It simply maps(adds) the additional columns that are described in abstract parent entity, onto child entities. In my usual usecase(createdDate, updadedDate column), this is the better approach since there's no reason to build a table for all datasets that have createdDate&updatedDate column. (A table of all the posts, announcements, comments, threads, re-replies, A2A, ...etc? Makes no sense)
However in your case you might want to keep a table of all kinds of employees. In that case use discriminatorcolumn & discriminatorvalue approach. Here's where #Inheritance(strategy=) annotation comes into play.
If #Inheritance doesn't exist, default inheritance strategy is SINGLE_TABLE. Which is self explanatory imo. All subclass entity columns are also added on this superclass (abstract entity) table. It will create a giant Employee table. Since it doesn't need join query it's faster and simpler in querying. But the down side is that table is giant and also there will be a lot of null values. (If OfficeWorker has column called 'OfficeLocation' and HomeWorker doesn't, then every HomeWorker rows will have OfficeLocation=null in the giant Employee table.)
What I used above is JOINED strategy. Also self explanatory. Makes a table of all Employee, a table of all OfficeWorker, a table of all HomeWorker. But in this case, Employee table only has common column values (id, name, .. what not) and type (OfficeWorker vs HomeWorker), and a foreignkey that is used for join query onto OfficeWorker table and HomeWorker table.
Last option is TABLE_PER_CLASS. It doesn't generate a table of all Employee. So it is the same as MappedSuperclass annotation but only more verbose. Never recommended.

#Column(name="alternative") on a filed of Embeddable class makes #AttributeOverrides in Entity class not necessary?

I have same-named field (int phone;) both in my entity class and in the embedded class. As per JPA Specification I shall use #AttributeOverrides in my entity over the embedded field.
The logic behind that: fields of embedded class are saved to the same database table as fields of entity class itself (loosely speaking adding one embedded filed to entity class looks like just adding all fields of the embeddable class to that entity), and thus we have two fields with the same default name for a respective column of the table used to store the entity in the database.
But what if I specify #Column(name="alternative_phone_name") either on the filed of Embeddable class, or on the filed of the Entity class - in such case there is no conflict and no #AttributeOverrides is necessary? Right? This is my question.
Secondly, what happens if I specify #Column(name="alternative_phone_name") on the filed of Embeddable class and then also redefine it with #AttributeOverrides over the embedded field inside the entity class? Is it an error and what would the database column name be in that case?
#Embeddable
class MyEmbeddable {
#Column(name="my_embed_phone")
int phone;
}
class MyEntity {
int phone;
// 1) do I need
// #AttributeOverrides({
// #AttributeOverride(name="phone", column="sth_different_than_phone")
// })
// 2) If I don't need it, would specifying it is error?
// Which overrides - this #AttributeOverrides or MyEmbeddable's #Column(name=...
#Embedded
MyEmbeddable embeddedFld;
}

Spring repository for super entity class not fetching all subclass entity types when changed from #MappedSuperClass to #Inheritance

We have an abstract #MappedSuperClass and bunch of entities extending it, like:
#MappedSuperclass
public abstract class SuperEntity implements Serializable {
#Id
private Long id;
private String name;
}
and lots of entities like:
#Entity
public class Sub[1..20]Entity extends SuperEntity {
...
}
Because of this there were created - as well - a bunch of repositories for each entity. All well this far.
Now there is a need to fetch all the entities that extend super. Therefore SuperEntity was changed as below:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class SuperEntity implements Serializable {
This should be functionally almost the same (is it?).
Then a new repository for this was created, like:
public interface SuperEntityRepository extends JpaRepository<SuperEntity, Long> { };
Now the problem is, when calling:
superEntityRepository.findAll();
it returns only about 5 of sub entities not all the 20. What could be wrong?
Upon writing the question I realized what was the problem. Values - including IDs - were inserted straight to the database and IDs were not unique in the scope of SuperEntity. That is why there is no #GeneratedValue, BTW. IDs were only unique in the scope of each extending sub class entity.
There were no error messages. Spring repository just picked up the first found id and all the other entities with same ID were ignored.
So the answer to have this working is to to update all the extending entities to have unique ID in the scope of the SuperEntity.
However, updating references cascading is quite a job so if there is a lighter way to get this working, share it.
Yes, I could have deleted the question but maybe someone finds it and this answer useful

JPA/Hibernate inheritance without id

Is it possible to make inheritance in JPA/Hibernate without id?
My use case:
I have multiple entities and I want every time change is being done, timestamp being recorded. So I created AbstractResource and want each derived class inherit properties and logic (to avoid writing same stuff over and over again in each class).
My problem that hibernate wants an ID to entity, and I do not care about id, since my only concern is additional properties. And each entity can have whatever id it wants (String, int, long, different name, etc.).
I tried with Embeddable, but looks like hibernate does not support inheritance for Embeddable. Do you have any ideas, how my task can be achieved?
My parent class from which "Audited" entities are derived:
#Embeddable
#EntityListeners(AbstractResourceListener.class)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class AbstractResource {
private long modifyTimestamp;
#Column(name = "_MODIFY_TIMESTAMP", nullable = true)
public long getModifyTimestamp() {
return modifyTimestamp;
}
public void setModifyTimestamp(long modifyTimestamp) {
this.modifyTimestamp = modifyTimestamp;
}
}
#MappedSuperclass is an annotation for super classes that you can extend and use in audit. Please see example

Spring Data JPA(Hibernate): How do I retrieve a concrete entity using only a field in its abstract superclass?

Consider the following hierarchy, where entities WidgetA and WidgetB extend an abstract Widget superclass:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Widget implements Serializable {
#Column(name="serialNumber", length=64, nullable=false, unique=true)
private String serialNumber;
...
and
#Entity
public class WidgetA extends Widget implements Serializable {
...
and
#Entity
public class WidgetB extends Widget implements Serializable {
...
I need to search for Widgets by serialNumber, but I don't know the concrete type of the Widget I'm searching for at runtime. What is the correct way to search for widgets by serialNumber such that if the serialNumber is that of a WidgetA, then an instance of WidgetA gets returned, and so on?
I am trying to use a findyBySerialNumber() in the Widget DAO, and I'm getting an error telling me I can't instantiate an abstract class, which makes sense, but I thought the persistence provider would know how to look in the concrete child entity tables and return the correct instance. Can I make it do this?
I am using "Spring Data JPA", so the Widget DAO is really simple:
public interface WidgetDAO extends JpaRepository<Widget, Long> {
public Widget findBySerialNumber(String serialNumber);
}
It seems you didn't specify a discriminator explicitly for your widget hierarchy. I think you can try to define it explicitly because Spring Data will manipulate bytecode to generate the queries, and so I suspect SpringData need to have those values explicitely defined.
Additionally in subclasses you need to indicate the discriminator value for each subclass.
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name="WIDGET_TYPE")
public abstract class Widget implements Serializable {
#Column(name="serialNumber", length=64, nullable=false, unique=true)
private String serialNumber;
...
-
#Entity
#DiscriminatorValue("A")
public class WidgetA extends Widget implements Serializable {
...
-
#Entity
#DiscriminatorValue("B")
public class WidgetB extends Widget implements Serializable {
...
Your objects and annotations look fine to me. I was able to take them, save a widget to a database, and fetch it out without a problem.
I think the the problem is in your data. Hibernate is probably finding a row in the Widget table that does not have a corresponding row in any of the sub class tables, and is therefore trying to create an instance of the superclass, and failing.
With InheritanceType.JOINED, you can either specify a discriminator, or let it do it for you (I believe by checking whether a row with that ID exists in the subclass table). But either way, you have to check your data to make sure there's not an entry in the super class table without a matching subclass row.
As a rule, I support #ben75's recommendation that you do explicitly specify #Discriminator for class hierarchies. It's better to have control over your discriminator values, so that later code changes don't alter the values in unexpected ways.
Hibernate supports that query just fine, and will happily return an instance of the correct subclass. Without knowing what it is in the Spring Data implementation that is trying to make an instance of Widget, you should be able to just declare the query and have it run it directly, rather than using the parser.
public interface WidgetDAO extends JpaRepository<Widget, Long> {
#Query("select w from Widget w where w.serialNumer = ?1")
public Widget findBySerialNumber(String serialNumber);
}

Categories