Abstract Exercise Entity:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Exercise {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
}
Resistance Exercise Entity:
@Entity
public class ResistanceExercise extends Exercise {
...
}
Duration Exercise Entity:
@Entity
public class DurationExercise extends Exercise {
...
}
Abstract Exercise Log Entity
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class ExerciseLog<T extends Exercise> {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
@ManyToOne
private T exercise;
}
Resistance Exercise Log Entity:
@Entity
public class ResistanceExerciseLog extends ExerciseLog<ResistanceExercise> {
...
}
Duration Exercise Log Entity:
@Entity
public class DurationExerciseLog extends ExerciseLog<DurationExercise> {
...
}
Exercise Log Repository:
public interface ExerciseLogRepository<T extends ExerciseLog<S>, S extends Exercise> extends JpaRepository<T, Long> {
}
The controller:
@RestController
@RequestMapping(value = "/api/v1/exercise-logs")
public class ExerciseLogController {
@Autowired
ExerciseLogRepository<ExerciseLog<Exercise>, Exercise> repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
public ResponseEntity<List<ExerciseLog<Exercise>>> getLogs() {
Pageable pageable = PageRequest.of(0, 20, Sort.unsorted());
Page<ExerciseLog<Exercise>> pageResult = repository.findAll(pageable);
return ResponseEntity.ok().body(pageResult.getContent());
}
}
With the above setup, and with multiple log types stored, when calling the endpoint of the controller, an exception is thrown (full stack trace here) along the lines of:
javax.persistence.EntityNotFoundException: Unable to find uk.deepblue.personaltrainer.domain.exercise.ResistanceExercise with id 8
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$JpaEntityNotFoundDelegate.handleEntityNotFound(EntityManagerFactoryBuilderImpl.java:159) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:227) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:278) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:121) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1240) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1123) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:682) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:464) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
at org.hibernate.type.ManyToOneType.resolve(ManyToOneType.java:239) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
There is no ResistanceExercise with id 8, that is a DurationExercise, and it is only referenced from the DurationExerciseLog table, so Spring/Hibernate seem unable to negotiate the underlying tables correctly.
I've tried many different configurations which ultimately end up with the same outcome. It looks like the TABLE_PER_CLASS inheritance strategy is the best option for what I'm trying to do (e.g. Using generics in Spring Data JPA repositories) which is the current setup I have (as above).
Is it even possible to do this with Polymorphic queries and generics or do I have to make a call for each ExerciseLog/Exercise combination I have?