How to handle Runtime Polymorphism in Spring Boot? - java

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.

Related

search though all tables extended from #MappedSuperclass

I have and issue with searching in entities that are extended from #MappedSuperclass. I created a class PhoneBook and extended 2 entities from it: FirstPhoneBook and SecondPhoneBook. The structure looks the following:
#MappedSuperclass
public abstract class PhoneBook {
...
#Entity
#Table(name = "first_phone_book")
public class FirstPhoneBook extends PhoneBook {
...
#Entity
#Table(name = "second_phone_book")
public class SecondPhoneBook extends PhoneBook {
...
These tables are absolutely similar. I discribe all fields in PhoneBook class, childs have only default constructor in it. External system sends a phone number as a parameter. Depending on whether tables contain such number or not my system responds with a word.
The question is: how can I search separately in each table that is extended from #MappedSuperclass without hardcoding each child class name?
I could only find variants of search by value like that:
currentSession.get(Employee.class, theId);
but there is explicit call to entity class. I want this to be extendable without need to write new DAO for each new entity added. Current method signature looks the following:
public <T extends PhoneBook> T findByNumber(String number);
What you describe is polymorphic queries, i.e. queries that reference the parent class. The Hibernate documentation says this is not well supported when using #MappedSuperclass inheritance:
Because the #MappedSuperclass inheritance model is not mirrored at the database level, it’s not possible to use polymorphic queries referencing the #MappedSuperclass when fetching persistent objects by their base class.
If polymorphic queries are frequently used, it's better to use the table per class inheritance strategy:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class PhoneBook {
...
#Entity
#Table(name = "first_phone_book")
public class FirstPhoneBook extends PhoneBook {
...
#Entity
#Table(name = "second_phone_book")
public class SecondPhoneBook extends PhoneBook {
...
You can then fetch an entity using the superclass:
PhoneBook phoneBook = currentSession.get(PhoneBook.class, theId);
and Hibernate would typically use a UNION to do the query with both tables.
This being said, even with #MapperSuperclass, Hibernate can still query all tables for classes that extend the parent class. You can use the following JPA query (note that it uses the fully qualified class name of the parent class):
Query<PhoneBook> query = currentSession.createQuery("from " + PhoneBook.class.getName() +
" where id = :id", PhoneBook.class);
query.setParameter("id", theId);
The difference is that here it's not querying an entity, but just all classes that extend a parent class. Also in this case, unlike with the table-per-class strategy, Hibernate will not use a UNION, but send a query to each table, in this case two separate SQL queries instead of one.

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

Implementing model inheritance on springboot have strange data repository behavior

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.

jpa2 hibernate use existing entity to load data from view

In my JPA2/Hibernate application I have table USERS and matching entity User + DAO.
Now, I created view V_USERS, which contains filtered data from table USERS.
Can I reuse entity User to query data from this view ?
No you can't. You can map two different classes into the same table but not the other way around.
The reason for this limitation is simple, how would JPA provider knew which table do you have in mind in the following query:
em.createQuery("SELECT u FROM User u");
However you might want to create a base class AbstractUser with all properties and have to empty subclasses: User and for instance VUser. The latter two classes will have a mapping to different tables/views. See MappedSuperclass and an example there.
#MappedSuperclass
public abstract class AbstractUser {
//all your columns/JPA mapping go here
}
#Entity
#Table(name="USERS")
public class User extends AbstractUser {}
#Entity
#Table(name="V_USERS")
public class VUser extends AbstractUser {}

Hibernate Annotation for Entity existing in more than 1 catalog

I have a Person entity mapped by Hibernate to a database table in a database catalog "Active". After a period of time, records in this database table in the "Active" catalog are archived/moved to an exact copy of the table in a database Catalog "History". I have the need to retrieve from both the Active and History Catalogs. Is there a better way to model this with Hibernate annotations than making an abstract class that 2 classes extend from.
This is what I have now.
#MappedSuperclass
public abstract class Person {
#Id
private Integer id;
private String name;
}
#Entity
#Table(name="Person", catalog="Active")
public class PersonActive extends Person {
}
#Entity
#Table(name="Person", catalog="History")
public class PersonHistory extends Person {
}
To my knowledge, that would be the right way to do it with annotations (you kinda have two tables so you need two entities). Then run a polymorphic query on the Person entity. I find this pretty clean by the way.
PS: Can you add a pointer on how to do this with mapping files, I'm really curious.
My thought would be to write a query to select both tables from db A and B. then create a query with hibernate and map it to your class.
example:
#Entity
#NamedNativeQuery(
name="GetAllPerson",
query="select * from A.Person inner join B.Person on A.Person.Id = B.Person.Id"
)
public class Person {
...
}
Not sure if it could work, your question made me also curious about the best way to do it :). I'll test it tonight after work and see if its any good.
I think there is a subproject of hibernate named shards. It is designed to work with multiple relational databases. If you want to use it, you may need big changes in your code.

Categories