Specify query hints for SimpleJpaRepository find methods

Viewed 374

Goal

I'm implementing a custom base repository with the goal of being able to specify attribute nodes of entity graphs by method argument instead of method annotation. So instead of having annotated methods like
@EntityGraph(attributePaths = ["enrollments"])
fun findByIdFetchingEnrollments(id: Long): Optional<Student>

in my repositories, I just have a base repository with a method like

fun findById(id: ID, vararg attributeNodes: String): Optional<T>

which gives a lot of flexibility because with only one method I can, for example, call

studentRepository.findById(1L, "enrollments") 
// or
studentRepository.findById(1L, "favouriteSubjects", "favouriteTeachers")
// and others...

where "enrollments", "favouriteSubjects", and "favouriteTeachers" are lazy-fetch entity fields by default but sometimes are required to be fetched eagerly (to avoid LazyInitializationException)

What I have

Those are my classes:
// Base repository interface. All my repositories will extend this interface

@NoRepositoryBean
interface Repository<T, ID> : JpaRepository<T, ID> {

    fun findById(id: ID, vararg attributeNodes: String): Optional<T>
    
    // other find methods

}
// Custom base class. Inherits SimpleJpaRepository

class RepositoryImpl<T, ID>(
        val entityInformation: JpaEntityInformation<T, Any?>,
        val em: EntityManager
) : SimpleJpaRepository<T, ID>(entityInformation, em), Repository<T,ID> {

    // This is basically a copy-paste of SimpleJpaRepository findById method implementation... 
    override fun findById(id: ID, vararg attributeNodes: String): Optional<T> {
    
        Assert.notNull(id, "The given id must not be null!")

        /*
        Because 'repositoryMethodMetadata!!.queryHints' is read-only, I have to create a new Map,
        put all the queryHints entries in it and put the 'javax.persistence.loadgraph' hint.
         */

        val graph = em.createEntityGraph(entityInformation.javaType)
        graph.addAttributeNodes(*attributeNodes)

        val hints: MutableMap<String, Any?> = HashMap()
        hints.putAll(repositoryMethodMetadata!!.queryHints)

        hints["javax.persistence.loadgraph"] = graph

        if (repositoryMethodMetadata == null) {
            return Optional.ofNullable(em.find(domainClass, id, hints))
        }

        val type = repositoryMethodMetadata!!.lockModeType

        return Optional.ofNullable(
                if (type == null) em.find(domainClass, id, hints)
                else em.find(domainClass, id, type, hints)
        )
    }

    /* 
    In order to implement the other find methods the same kind of copy-paste implementations
    needs to be done, which shouldn't be necessary but I'm not seeing how.
    */

}
@Configuration
@EnableJpaRepositories(
        "com.domain.project.repository",
        repositoryBaseClass = RepositoryImpl::class)
class ApplicationConfiguration

Problem

The problem is that I'm not seeing a simple way of adding the javax.persistence.loadgraph hint to the query hints that SimpleJpaRepository pass to EnitityManager.find.

I think the best way to solve this would be override the SimpleJpaRepository getQueryHints() method which is used by all find methods in SimpleJpaRepository. Then my code in RepositoryImpl would become much simpler (override all find methods, and for each just add the hint and call super method). But I can't override it because QueryHints class is package-private.

If SimpleJpaRepository metadata property were mutable, I could also add to it query hints (which in turn are considered in getQueryHints() method), but all its properties are read-only and I think sometimes metadata is null (not sure on this but it is marked as @Nullable).

0 Answers
Related