hibernatejpaspring-data-jpaspring-dataself-referencing-table

Self referencing JPA Entity to satisfy interface definition


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.


Solution

  • 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.