1

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:

design of inheriting entities with seperate tables

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?

snaeil
  • 351
  • 2
  • 11

2 Answers2

2

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;
    }

}

    
crizzis
  • 9,978
  • 2
  • 28
  • 47
  • Wow, I didn't expect the answer to be that easy. Thank you! – snaeil Mar 17 '21 at 09:18
  • Since I am not yet too familiar with generics, how would you define a setter for `Animal` and `Institution`? – snaeil Mar 17 '21 at 10:46
  • 1
    Just define regular setters in subclasses. Or, if you really insist on setters in the superclass, you could declare `Animal` as `public class Animal` and use `abstract void setInstitution(T institution)` as well as `abstract T getInstitution()` – crizzis Mar 17 '21 at 15:13
  • Worked for me! Thanks again – snaeil Mar 19 '21 at 07:41
1

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.

Montaser Sobaih
  • 305
  • 2
  • 7