My domain contains FieldValidator objects, that have sub classes EmailValidator, SizeValidator etc. There is only one instance of FieldValidator per subclass.
#Entity
#Immutable
#Table(name = "FIELD_VALIDATOR")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "TYPE")
public abstract class FieldValidator extends AbstractEntity implements IFieldValidator{
#Column(unique=true, name="TYPE")
public String type;
public String name;
public String argumentName;
//To override by subclasses
public abstract Optional<CustomError> run(FieldConstraint constraint);
/**
* Constructor, getters and setters omitted
*/
}
Example subclass
#Entity
#DiscriminatorValue("DATE_BEFORE")
public class DateBeforeValidator extends FieldValidator {
public DateBeforeValidator() {
super("Date", "Before");
}
public Optional<CustomError> run(FieldConstraint constraint) {
//TODO
}
}
Example subclass
#Entity
#DiscriminatorValue("SIZE_MIN")
public class SizeMinValidator extends FieldValidator {
public SizeMinValidator() {
super("Size", "Minimum");
}
public Optional<CustomError> run(FieldConstraint constraint) {
//TODO
}
}
How can I initialise the FIELD_VALIDATOR table with one entity per FieldValidator subclass? I need ensure that: the FIELD_VALIDATOR table is immutable (no other data can be added or deleted to/from the table), each FieldValidator entity is unique and immutable (the #Column(unique=true) and the #Immutable annotations should do the trick).
I think your best bet is JPAs/Hibernates initial load feature. You can read about it here for example.
Related
I have read through many questions and tried to solve the following problem with Java generics and #MappedSuperclass. Important for me is that I have to have different tables as you would get with TABLE_PER_CLASS.
This is the goalI want to achieve in Spring-Boot:
I am stuck because examples like these are not representing my more complex goal from the image above.
The different inheriting classes (ZooAnimal, CircusAnimal) have attributes that must not be null AND must not be used in the respective opposite class.
E.g. an object of ZooAnimal may have
#NotNull
int numberOfZoosSeen;
and an object of CircusAnimal may have
#NotNull
boolean hasEatenClown;
That's why they need their own database tables.
Is it technically possible and if no, is there a better design that ensures that the entities Zoo, Circus, ZooAnimal and CircusAnimal have their own tables?
Since Animal and Institution are abstract, I would question the need to declare any association mappings in them directly.
You could simply use the following approach:
#MappedSuperclass
public abstract class Institution {
#Id
#GeneratedValue
private int id;
public abstract List<? extends Animal> getAnimals();
...
}
#Entity
public class Zoo extends Institution {
#OneToMany(mappedBy = "institution")
private List<ZooAnimal> animals;
public List<ZooAnimal> getAnimals() { //covariant type
return animals;
}
}
#MappedSuperclass
public abstract class Animal {
#Id
#GeneratedValue
private int id;
public abstract Institution getInstitution();
...
}
#Entity
public class ZooAnimal extends Animal {
#ManyToOne
private Zoo institution;
public Zoo getInstitution() { //covariant type
return institution;
}
}
Yes it's possible to implement these classes using TABLE_PER_CLASS strategy with generics, but you have to use #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) annotation instead of #MappedSuperClass.
To know more about TABLE_PER_CLASS strategy visit jpa advanced mappings
To prepare your abstract classes using generics all you have to do is understand the relation between classes before using generics. After that you have to understand recursive generics concept to implement your case using generics.
For more information's visit introduction to generics
Here is the implementation of classes:-
Institution class
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Institution<T extends Institution<T, S>, S extends Animal<S, T>> {
#Id
#GeneratedValue
private int id;
//add properties, constructors and methods
#OneToMany(targetEntity = Animal.class, mappedBy = "institution", cascade = CascadeType.ALL)
private List<S> animals;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public List<S> getAnimals() {
return animals;
}
public void setAnimals(List<S> animals) {
this.animals = animals;
}
}
Animal class
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Animal<T extends Animal<T, S>, S extends Institution<S, T>> {
#Id
#GeneratedValue
private int id;
//add properties, constructors and methods
#ManyToOne(targetEntity = Institution.class, cascade = CascadeType.ALL)
private S institution;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public S getInstitution() {
return institution;
}
public void setInstitution(S institution) {
this.institution = institution;
}
}
Important Note:- During implementing the entities using generics you must specify the targetEntity class in #OneToOne, #OneToMany, #ManyToOne and #ManyToMany annotations as shown in the above code because JPA cannot detect the target entity in generics.
Zoo and ZooAnimal classes
#Entity
public class Zoo extends Institution<Zoo, ZooAnimal> {
//add properties, constructors and methods
}
#Entity
public class ZooAnimal extends Animal<ZooAnimal, Zoo> {
//add properties, constructors and methods
}
Circus And CircusAnimal
#Entity
public class Circus extends Institution<Circus, CircusAnimal> {
//add properties, constructors and methods
}
#Entity
public class CircusAnimal extends Animal<CircusAnimal, Circus> {
//add properties, constructors and methods
}
As you can see in the implementation of subclasses, there is no need to override getAnimals() and getInstitution() methods to change their return types because we already specified its return types using generics.
I have a few tables named MEMBER, PROVIDER, EMPLOYER etc and they have few common columns like ID, ADDRESS etc. I'm using hibernate to fetch data from these tables using Spring Data JPA. Since I have many similar tables, I created a super entity class and wrote a common method to fetch the data. Here is my super class.
#Entity
#Getter
#Setter
#NoArgsConstructor
#ToString
public abstract class Recipient implements Serializable {
#Id
#Column(name = "RECIPIENT_ID", unique = true, nullable = false)
private Long id;
}
Here is a sample entity which extends this super class.
#Entity
#Getter
#Setter
#NoArgsConstructor
#ToString
#Table(name = "MEMBER")
public class Member extends Recipient {
#Column(name = "Member_NAME")
private String membername;
}
Here is the Service
public class RecipientServiceImpl extends AbstractService<Recipient>
implements RecipientService {
#Override
public Recipient findByIdAndType(long id, String type) {
Recipient recipient = null;
switch (RecipientType.get(type)) {
case MEMBER:
recipient = recipientRepository.findMemberById(id);
case PROVIDER:
recipient = recipientRepository.findProviderById(id);
case EMPLOYER:
recipient = recipientRepository.findEmployerById(id);
}
}
And here is the repository
public interface RecipientRepository
extends JpaRepository<Recipient, Long>, JpaSpecificationExecutor<Recipient> {
public Member findMemberById(long id);
public Provider findProviderById(long id);
public Employer findEmployerById(long id);
}
Obviously it didn't work since there weren't any tables mapped in the Recipient abstract entity. Is there a way to implement what I was hoping for as a generic fetching method instead of having to create repositories for each entities ?
I'm trying to mapp Entity (TrainingEntity) to DTO, where one of the fields is a Set with ManyToMany reference to an AbstractEntity (CoachEntity) divided by Single Table into two subclasses: ExternalCoach and InternalCoach.
Both subclasses have different data, therefore require two different mappers.
#Entity
#Table(name = "TRAINING")
public class TrainingEntity extends AbstractEntity {
#ManyToMany()
#JoinTable(name = "TRAINING_EMPLOYEE", joinColumns = { #JoinColumn(name = "TRAINING_ID") }, inverseJoinColumns = {
#JoinColumn(name = "COACH_ID") })
private Set<CoachEntity> coachEntities;
#Column(nullable = false)
private TrainingType trainingType;
......some data....
}
Abstract Coach Entity
#Entity
#Table(name = "COACH")
#DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.STRING)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class CoachEntity extends AbstractEntity {
......some data....
}
Coach Subclasses:
#Entity
#DiscriminatorValue("EXTERNAL")
public class ExternalCoachEntity extends CoachEntity {
......some data....
}
#Entity
#DiscriminatorValue("INTERNAL")
public class InternalCoachEntity extends CoachEntity {
......some data....
}
One mapper for Abstract Coach class won't have acces to subclasses methods and fields, so I need two different for External and Internal. Than I would have to use them in TrainingMapper class, but (example of internal):
public class CustomTrainingMapper {
public static TrainingDto toTrainingDto(TrainingEntity trainingEntity){
if(trainingEntity == null){
return null;
}
if(trainingEntity.getTrainingType().equals(TrainingType.INTERNAL)){
Set<CoachEntity> coachEntities = trainingEntity.getCoachEntities();
Set<CoachDto> coachDtos = CustomInternalCoachMapper.toDTOSet((Set<InternalCoachEntity>)coachEntities);
}
I get:
cannot cast from Set<CoachEntity> to Set<InternalCoachEntity>
Without cast it simply doesn't see mappers methods with subclass input.
The method toDTOSet(Set<InternalCoachEntity>) in the type CustomInternalCoachMapper is not applicable for the arguments (Set<CoachEntity>)
When in mapper I change method input to abstract Coach Class it doesn't see subclasses methods and fields.
Part of InternalMapper:
public class CustomInternalCoachMapper {
public static CoachDto toCoachDto(InternalCoachEntity coachEntity) {
if (coachEntity == null) {
return null;
}
EmployeeDto employeeDto = CustomEmployeeMapper.toEmployeeDto(coachEntity.getEmployeeEntity());
return new InternalCoachDto(coachEntity.getId(), coachEntity.getVersion(), coachEntity.getCreateDate(),
coachEntity.getUpdateDate(), coachEntity.getName(), coachEntity.getSurname(), employeeDto);
}
Is it possible to mapp this AbstractEntity Set into subclasses DTOs?
I also tried with AbstractDto for Coaches, but then I'm facing the same problem with no access to subclasses getters and setters.
I'm trying to map a class hierarchy to a single table using Hibernate and one table not creating. I add #Inheritance(strategy=InheritanceType.SINGLE_TABLE) but in base there are separate tables.
#MappedSuperclass
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public abstract class User implements UserDetails {
#Id #GeneratedValue
private int id;
...
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
...
}
#Entity
public class Manager extends User{
...
}
#Entity
public class Administrator extends User{
...
}
Whats wrong?
Your User class should be
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(field = "type", discriminatorType = DiscriminatorType.STRING)
public class User implements UserDetails {
/* your original stuff */
}
Then you need to map the extended classes as follows:
#Entity
#DiscriminatorValue("manager")
public class Manager extends User { /* stuff */ }
#Entity
#DiscriminatorValue("administrator")
public class Administrator extends User { /* stuff */ }
This effectively should create a single table which houses all the fields from User, Manager, and Administrator using a special field that is added by Hibernate called type which will hold values of either manager or administrator.
I have a superclass that is an #Entity, something like this one:
#Entity
#Table(name = "utente")
public class Utente implements Serializable{
...
}
The subclass has just a #Transient field besides:
#Entity
public class UtenteSub extends Utente{
#Transient
private String newField;
}
To make it work, I should add #DiscriminatorValue, #Inheritance and add a field on the table.
This is a lot of work to do, taking into account that all I need in the subclass is just a #Transient field (I need it to "check" the object Utente after its "submission" in a form).
Is there a better and easier way to extend the #Entity in my scenario?
Thank you.
You could try creating an abstract base class UtenteBase:
#MappedSuperClass
public abstract class UtenteBase implements Serializable
{
//all mapped columns go here
}
All your mapped columns which were in Utente before are now in this class.
You can then extend this class with your two above mentioned classes:
#Entity
#Table(name = "utente")
public class Utente extends UtenteBase
{
public Utente {}
}
#Entity
#Table(name = "utente")
public class UtenteSub extends UtenteBase
{
#Transient
private String newField;
}
The class Utente is the concrete implementation class and is used for the communication with the database.
Both classes are in the same inheritance tree and you don't need to add a DiscriminatorValue and change the table.