Spring Data JPA позволяет вручную определять запрос, который будет выполнен методом репозитория через аннотацию @Query
. К сожалению, связывание параметров в JPQL весьма ограничено установкой значения и конвертацией некоторых типов. Последний выпуск Spring Data JPA M1 из Evans выпуска устраняет эту проблему, добавляя поддержку использования SpEL выражений для использования динамического связывания параметров в аннотациях @Query
методов, предоставляя дополнительную гибкость, когда запросы определяются вручную. В этой статье, я расскажу вам о применимости этой возможности.
Выражения параметра метода
Поддержка SpEL предоставляет доступ к аргументам запроса метода. Это позволяет вам либо просто связать параметр как есть, либо выполнить дополнительные действие перед привязкой.
@Query("select u from User u where u.age = ?#{[0]}")
List findUsersByAge(int age);
@Query("select u from User u where u.firstname = :#{#customer.firstname}")
List findUsersByCustomersFirstname(@Param("customer") Customer customer);
Параметры индексируются([0]
в первом методе) или имя определяется через @Param
. В настоящем SpEL выражении привязка делается либо через ?#
, либо через :#
. Мы поддерживаем оба варианта, чтобы вы смогли согласовать при привязке стандартный параметр JPQL, который может быть в определении запроса. Параметры особых типов, подобно Sort
и Pageable
представляют имена классов как переменные.
Расширенные SpEL выражения
Не смотря на то, что расширенная привязка параметра является полезной возможностью, реальная сила SpEL исходит из того факта, что выражения могут ссылаться на абстракции фреймворка или другие компоненты приложения. Основным сценарием для SpEL является определение ограничений безопасности. Поэтому было бы круто, если бы мы могли ограничить запрос только результатом в соответствии с текущим авторизованным пользователем:
@Query("select u from User u where u.emailAddress = ?#{principal.emailAddress}")
List findCurrentUserWithCustomQuery();
Как видите, мы ссылаемся на свойство Spring Security principal
, т.к. Spring Data SpEL поддерживает интеграцию с Spring Security.
Модель расширения SpEL EvaluationContext
Spring Data имеет точку расширения EvaluationContextExtension
.Этот интерфейс позволяет реализовать собственный EvaluationContext
очень детально, но для удобства мы предоставляем базовый класс EvaluationContextExtensionSupport
, чтобы вы могли реализовать только то, что вам необходимо:
class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport {
@Override
public String getExtensionId() {
return "security";
}
@Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {};
}
}
Для нашего Spring Security расширения мы наследовали EvaluationContextExtensionSupport
и переопределили метод getRootObject()
и вернули новый экземпляр SecurityExpressionRoot
, который предоставляет все свойства и методы по безопасности, которые вы уже знаете из использования в @PreAuthorize
. Этот этап также делает их доступными в SpEL выражениях в @Query
аннотации.
Последнее, что мы должны сделать, это зарегистрировать расширение безопасности как бин:
@Configuration
@EnableJpaRepositories
class SecurityConfiguration {
@Bean
EvaluationContextExtension securityExtension() {
return new SecurityEvaluationContextExtension();
}
}
Spring Data JPA будет собирать все бины типа EvaluationContextExtension
и использовать для подготовки EvaluationContext
, чтобы они были использованы SpEL выражением, определенном в @Query
.
Установленное расширение теперь позволит нам использовать всю мощь возможностей Spring Security SpEL. Представим запрос метода репозитория, который определенный список BusinessObject
объектов для текущего пользователя и все объекты того же типа, если текущий пользователь является администратором. Запрос метода будет выглядеть примерно так:
interface SecureBusinessObjectRepository extends Repository<BusinessObject,Long>{
@Query("select o from BusinessObject o where o.owner.emailAddress like "+
"?#{hasRole('ROLE_ADMIN') ? '%' : principal.emailAddress}")
List<BusinessObject> findBusinessObjectsForCurrentUser();
}
Вы можете найти рабочие примеры фрагментов, указанных в этой статье, из примеров Spring Data.
Сложные случаи использования
Часто новые возможности содержат способы делать вещи, которые раньше невозможно было реализовать, как пример, нумерация в нативных запросах. Поскольку этот механизм предоставляет особые типы параметров, такие как Sort
или Pageable
, теперь мы можем использовать нумерацию в нативных запросах. Примером этому может служить UserRepository.
Что дальше?
В настоящее время мы исследуем более тесную интеграцию Spring Security в Spring Data. Мы также работаем над добавлением поддержки SpEL совместимости в других Spring Data модулях.