Цель
Я реализую настраиваемый базовый репозиторий с целью иметь возможность указывать узлы атрибутов графов сущностей по аргументу метода вместо аннотации метода. Поэтому вместо аннотированных методов, таких как
@EntityGraph(attributePaths = ["enrollments"])
fun findByIdFetchingEnrollments(id: Long): Optional<Student>
в моих репозиториях, у меня просто есть базовый репозиторий с таким методом, как
fun findById(id: ID, vararg attributeNodes: String): Optional<T>
, который дает большую гибкость, потому что только с одним методом я могу, например, вызовите
studentRepository.findById(1L, "enrollments")
// or
studentRepository.findById(1L, "favouriteSubjects", "favouriteTeachers")
// and others...
, где "enrollments"
, "favouriteSubjects"
и "favouriteTeachers"
по умолчанию являются полями объекта с отложенной выборкой, но иногда их требуется извлекать быстро (чтобы избежать LazyInitializationException
)
Что у меня есть
Это мои классы:
// 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
Проблема
Проблема в том, что я не вижу простого способа добавления подсказки javax.persistence.loadgraph к подсказкам запроса, которые SimpleJpaRepository передают в EnitityManager.find.
Я думаю, что лучший способ решить эту проблему - переопределить SimpleJpaRepository getQueryHints()
, который используется всеми методами find в SimpleJpaRepository
. Тогда мой код в RepositoryImpl
стал бы намного проще (переопределить все методы find , и для каждого просто добавить подсказку и вызвать супер-метод). Но я не могу его переопределить, потому что класс QueryHints
является закрытым для пакета.
Если свойство SimpleJpaRepository metadata
было изменяемым, я мог бы также добавить к нему подсказки запросов (которые, в свою очередь, рассматриваются в методе getQueryHints()
), но все его свойства доступны только для чтения, и я думаю, что иногда metadata
имеет значение null (не уверен в этом, но он помечен как @Nullable
).