2

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?

Drew
  • 464
  • 1
  • 5
  • 18
  • Where are the concrete classes of ExerciseLog? – Simon Martinelli Aug 16 '18 at 12:24
  • Whoops, thanks @SimonMartinelli, I hadn't noticed I'd copied the exercise ones in twice by mistake. Updated accordingly. – Drew Aug 16 '18 at 13:30
  • Why do you have @ManyToOne private T exercise; in your abstract base log class? – Simon Martinelli Aug 16 '18 at 13:58
  • This is left over from when I tried with the JOINED inheritance strategy. Rather than having @ManyToOne private T exercise on the abstract base class, I moved it to the concrete classes. This did remove this particular issue, however it prevents generic operations using exercise if it doesn't appear at the base class level. I can confirm that removing from the concrete classes still causes this exception to be thrown. – Drew Aug 16 '18 at 16:22
  • I've removed the @ManyToOne private exercise from the concrete class in the question as it was confusing. – Drew Aug 16 '18 at 16:29
  • 1
    Presumably, Hibernate is the JPA provider. If yes, change `@ManyToOne` to `@ManyToOne(targetEntity = Exercise.class)`. Since `Exercise` is `abstract`, this will force the JPA provider to find its exact implementation when deserializing records. Also consider replacing `@Entity` on abstract classes with `@MappedSuperclass`, although that does not have any bearing on this question. – manish Aug 17 '18 at 04:45

1 Answers1

1

Thanks to Manish for the suggestion, adding the targetEntity=Exercise.class has fixed this problem (though changing to @MappedSuperclass isn't appropriate here as it would prevent Exercise entities being used elsewhere in other scenarios)

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class ExerciseLog<T extends Exercise> {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    protected Long id;

    @ManyToOne(targetEntity=Exercise.class)
    private T exercise;
}
Drew
  • 464
  • 1
  • 5
  • 18