0

I have a spring + Data JPA setup which contains several Entities that inherit from interface definitions like this (in “pseudo” Java):

@Entity 
A implements WithSubProperty
@Id
Long Id
SubProperty subProperty
…

@Entity
B implements WithSubProperty
@Id
Long Id
SubProperty subProperty
…

Interface WithSubProperty
SubProperty subProperty
…

@Entity
SubProperty
@Id
Long Id
…

Each entity has its own table. In addition, there are several repository fragments that make use of the interface definition (in order to implement custom logic only once for compatible classes) like this:

RepositoryFragment<WithSubProperty, K>
List<WithSubProperty> findAllWithSubProperty(SubProperty subProperty)
…

Now, I’d like to handle the actual interface property (SubProperty) the same way without having to duplicate and adapt the RepositoryFragment logic for this single entity. I tried the following (using several different annotations (@OneToOne, @Transient, @Column, @JoinColumm, @Formula and combinations thereof on SubProperty):

@Entity    
SubProperty implements WithSubProperty
@Id
Long Id
@XXXAnnotations
SubProperty subProperty

One idea was to reference the id column, basically forming a OneToOne relationship to itself. So far, I could not get it to work. At first glance, this approach looks a bit awkward but afaics there should not be issues other than the presumably(?) lacking support by JPA/Hibernate and the risk of ending up in a recursion. Ideas? Thanks!

Update: Here is a minimal (not) working example: https://github.com/user462982/demoJPA/blob/master/src/main/java/com/example/demo/entities/SubProperty.java

I'm sorry for the confusion the code above might have caused. My original code is in Kotlin and I tried to translate that to Java from the top of my head without anticipating/considering how boilerplate Java really gets (e.g. there are no interface properties in Java)... Hope the working example makes it clearer now. It creates the same Exceptions upon starting, so I assume the (byte)code is equivalent.

user462982
  • 1,635
  • 1
  • 16
  • 26
  • I'm not quite sure what mapping you try to achieve. How many tables do you want? `A` and `B`, or `A` and `B` and `SubProperty`. Have you considered using a `MappedSuperclass` for your `WithSubProperty` interface? Anyway, I don't think you'd want to introduce a OneToOne relationship, unless you actually have a relationship between entities (e.g. a Person is related to one other Person). Generally, it should be possible in Spring Data to create a shared base repository to avoid duplicating code, without adding a new relationship to your mapping. – Dario Seidl Oct 27 '20 at 18:06
  • What @DarioSeidl said, plus if you want to handle all entities with just one repository method, you can try experimenting with `@Query("SELECT e FROM #{#entityName} WHERE e.subProperty = :subProperty")` – crizzis Oct 27 '20 at 18:08
  • I linked a MWE to make the situation more clear. @DarioSeidl Since SubProperty is just one of many possible interfaces, a MappedSuperclass would not work. I actually don't want any relationship, these would be just work arounds to enable referencing the entity (instance) by itself in a somewhat JPA compliant way. – user462982 Oct 28 '20 at 13:54
  • OK, not directly answering your question, but personally I've found that using (too much) inheritance in JPA/Hibernate isn't working well (it's difficult to get the mappings right, and then even more difficult to get the performance right). I stick to at most one level of (single table) inheritance. You *can* have multiple MappedSuperclasses in a hierarchy and that works well to share some common fields, but it's true you cannot compose multiple interfaces like that. Maybe consider composition (e.g. with Embeddables) over inheritance here? – Dario Seidl Oct 28 '20 at 16:54

1 Answers1

0

Finally I was able to find a solution: https://github.com/user462982/selfReferencingJPAEntity/blob/master/src/main/kotlin/ex/ample/selfreferencing/entites/Property.kt (this time in Kotlin as Java was just too much pita...).

Basically

@OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id")

     @ManyToOne(fetch = FetchType.LAZY)
     @JoinColumn(name = "id", insertable=false, updatable =false)

was enough:

@Entity class Property : WithProperty {

@Id
@GeneratedValue
val id: Long? = null

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id", insertable=false, updatable =false)
override val property: Property? = null

A good starting point to understand the issue is the test: https://github.com/user462982/selfReferencingJPAEntity/blob/master/src/test/kotlin/ex/ample/selfreferencing/repositories/RepositoriesTest.kt

The test also revealed a bug in org.hibernate.loader.hql.QueryLoader which seems to get confused if a parameter appears more than once in a query with a self referencing entity like above. The hibernate issue is caused by @OneToOne association and can be solved by using @ManyToOne as above instead.

user462982
  • 1,635
  • 1
  • 16
  • 26