Cosium / spring-data-jpa-entity-graph

Spring Data JPA extension allowing full dynamic usage of EntityGraph on repositories
MIT License
478 stars 49 forks source link

In Kotlin, specifying a default entity graph make query fail with Stackoverflow exception #119

Open mr-nothing opened 1 year ago

mr-nothing commented 1 year ago

For some reason specifying DynamicEntityGraph as a default one make repository.findById(id: String) fail with stackoverflow exception. Was unable to understand if it is a bug or I just misuse/misconfigured something.

Steps to reproduce the behaviour:

  1. Define repository as follows:

    @Repository
    interface MyEntityRepository : EntityGraphJpaRepository<MyEntity, String> {
    override fun defaultEntityGraph(): Optional<EntityGraph> {
        return Optional.of(
            DynamicEntityGraph.loading(listOf("field1", "field2", "field3", "field4"))
        )
    }
    // Here goes some custom methods
    }

    Where fieldN is a OneToOne relation fields.

  2. Call findById repository method as follows:

    class MyService(
    private val myEntityRepository: MyEntityRepository
    ) {
    fun getMyEntityById(entityId: String): MyEntity {
    return myRepository.findById(entityId).orElseThrow {
        NotFoundException("Entity not found")
    }
    }
  3. Make this method be called by any of your rest endpoints.

What is the expected output ?

DynamicEntityGraph should be used to fetch MyEntity data.

What happens instead ?

Query fails with stackoverflow error. error.log

Environment

Link to an automated test demonstrating the problem

Unfortunately I was unable to reproduce this on sample project and I can't share my project. Will try to investigate this further but maybe stacktrace can be of some help to understand what is going on.

Additional context

n/a

mr-nothing commented 1 year ago

BTW This is probably somehow related to kotlin(the project I'm working on is written in Kotlin), since spring may create some additional proxies for repositories in Kotlin (or can do this in a specific way) and, as a consequence, DefaultEntityGraphMethods can't do it's job to call defaultEntityGraph to get default entity graph for repository.

reda-alaoui commented 1 year ago

Hello @mr-nothing ,

It looks like we need to add a recursive gard. But we have to imagine an automated test allowing to reproduce the issue first.

mr-nothing commented 1 year ago

Hello, @reda-alaoui and thanks a lot for reply I was able to fix this issue with use of some kotlin compiler compatibility tweaks. The only possible way to make default entity graphs work in kotlin project is to implement repository like this:

@Repository
@JvmDefaultWithoutCompatibility <<<---------------- This line makes all the magic
interface MyEntityRepository : EntityGraphJpaRepository<MyEntity, String> {
    override fun defaultEntityGraph(): Optional<EntityGraph> {
        return Optional.of(
            DynamicEntityGraph.loading(listOf("field1", "field2", "field3", "field4"))
        )
    }
// Here goes some custom methods
}

Instead of @JvmDefaultWithoutCompatibility you can use @JvmDefaultCompatibility or deprecated @JvmDefault. Besides this kotlin compiler need to be instructed to use jvm-default flag:

compileKotlin {
    kotlinOptions.freeCompilerArgs = ['-Xjvm-default=all']    <<<---There are some options for jvm-default flag, refer documentation
}

All the stuff above makes kotlin compiler generate java 8 compatible default interface methods instead of public static final (kinda companion) class that contains this implementation. See SO thread for more details.

I will try to make a test case for this issue so we can understand how we can defend this lib from stackoverflow errors with kotlin. Also it would be nice to mention this kind of issues in documnetation.