I am using Entity Inheritance for the first time and stuck with using a MappedSuperClass as collection inside another MappedSuperClass.
I have two similar set of Entities which must save data to different tables in DB. But since the business logic is perfectly same, so I want to use base classes for these entities, so these base classes can be used in common JPA repository (#NoRepositoryBean).
#MappedSuperClass
class Classroom {
String std;
}
#MappedSuperClass
class School {
String name;
#OneToMany //ERROR
Set<Classroom> rooms;
}
#entity
#table("A_SCHOOL")
class ASchool extends School {
Set<AClassroom> rooms;
}
#entity
#table("A_CLASSROOM")
class AClassroom extends Classroom {
}
#entity
#table("B_SCHOOL")
class BSchool extends School {
Set<BClassroom> rooms;
}
#entity
#table("B_CLASSROOM")
class BClassroom extends Classroom {
}
The issue is the #OneToMany mapping in School is not allowed since Classroom is not an entity.
Considering the common JPA repository (and all other business logic) is designed to work with only School/Classroom, how should I update the association of Classrooms in School?
the idea of using #MappedSuperClass is to make common attributes between different classes in one place .
in your case each School has dependency on certain ClassRoom so you need to declare that relationship in each subclass of school,
so you need to remove the relationship from the parent class and declare it in all School subclasses .
have a look at this tutorial :How to inherit properties from a base class entity using #MappedSuperclass with JPA and Hibernate
Related
I have a model like this:
class Group {
#Id
#GeneratedValue
Long id
#OneToMany()
#JoinColumn()
List<Base> baseClasses;
}
This is the base class:
#MappedSuperclass //tried to add strategy as well table per super class as well but it does not work
abstract class Base{
//some fields here
}
The base class has two levels of children:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
class FirstLevel extends Base{
#Id
#GeneratedValue
Long id
//some fields here
}
#Entity
class SecondLevel extends FirstLevel{
#Id
#GeneratedValue
Long id
//some fields here
}
I am using spring data (hibernate). What I would like to achieve is map this class hierarchy into database structure like this:
table Group which has one to many reference to specific base class
specific base class - SecondLevel table
I would like to map this two level of Base hierarchy into one table which next will be accessible through #OneToMany mapping inside group table.
When I have #MappedSuperclass I am getting the following error:
is org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany
targeting an unmapped class:
When I replace MappedSuperclass with Inheritance strategy -> TABLE_PER_CLASS
Hibernate requires two tables - FirstLevel and SecondLevel which I dont want.
To sum up - How do I map polymorphic relation oneToMany to multiple level of inheritance hierarchy ?
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.
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.
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
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.