Поддержка SpEL в Spring Data JPA @Query

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 модулях.

comments powered by Disqus