Spring Data JPA @Query 定义中的 SpEL 支持

工程 | Thomas Darimont | 2014年7月15日 | ...

Spring Data JPA 允许使用 @Query 注解手动定义由 Repository 方法执行的查询。遗憾的是,JPQL 中的参数绑定相当有限,只允许设置值并提供一些类型转换。Evans 发布列车的最新 Spring Data JPA M1 版本通过添加 SpEL 表达式支持来缓解这一痛点,以便在 @Query 注解语句中使用动态绑定参数,这在手动定义查询时提供了额外的灵活性。在本篇博文中,我将向您介绍此功能的能力。

方法参数表达式

SpEL 支持提供了对查询方法参数的访问。这允许您直接绑定参数,或者在绑定之前执行其他操作。

@Query("select u from User u where u.age = ?#{[0]}")
List<User> findUsersByAge(int age);

@Query("select u from User u where u.firstname = :#{#customer.firstname}")
List<User> findUsersByCustomersFirstname(@Param("customer") Customer customer);

参数可以通过索引访问(在第一个方法中是 [0])或通过使用 @Param 声明的名称进行访问。实际的 SpEL 表达式绑定通过 ?#:# 触发。我们支持这两种类型,以便与查询定义中可能出现的标准 JPQL 参数绑定保持一致。SortPageable 等特殊类型的参数以其简单的类名作为变量公开。

高级 SpEL 表达式

虽然高级参数绑定是一个非常有用的功能,但 SpEL 的真正强大之处在于表达式可以引用框架抽象或其他应用程序组件。SpEL 的一个非常常见的场景是定义安全约束。因此,如果我们可以将查询限制为仅返回与当前已认证用户相关的结果,那将非常棒

@Query("select u from User u where u.emailAddress = ?#{principal.emailAddress}")
List<User> 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 时已经了解的所有安全属性和表达式。这一步也使它们在我们 @Query 注解的 SpEL 表达式中可用。

我们需要采取的最后一步是将安全扩展注册为一个 Bean

@Configuration
@EnableJpaRepositories
class SecurityConfiguration {

    @Bean
    EvaluationContextExtension securityExtension() {
        return new SecurityEvaluationContextExtension();
    }
}

Spring Data JPA 将拾取所有类型为 EvaluationContextExtension 的 Bean,并使用它们准备用于评估 @Query 中定义的 SpEL 表达式的 EvaluationContext

到位的扩展现在将让您充分利用 Spring Security SpEL 函数的全部功能。想象一个 Repository 查询方法,它应返回当前用户拥有的 BusinessObject,或者如果当前用户是管理员,则返回所有 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-Examples Repository 中找到此处所示代码片段的实际示例。

复杂用例

新特性常常能实现以前认为不可能的事情——例如在原生查询中进行分页。由于该机制也暴露了 SortPageable 等特殊参数类型,我们现在可以在原生查询中使用分页。相关示例可以在这里找到 UserRepository

接下来是什么?

目前我们正在研究将 Spring Security 更紧密地集成到 Spring Data 中。我们还在努力为其他 Spring Data 模块添加 SpEL 功能支持。

现在轮到您了——请在下方评论中告诉我们您的想法,或者在我们的 JIRA 中提交功能请求。

SpringOne 2GX 2014

SpringOne 2GX 2014 临近了。

如果您想了解更多关于 Spring Data 的信息,请务必注册参加今年的 SpringOne 大会。日程表中包含许多与数据相关的讲座,将向您介绍 Evans 版本即将发布的新特性。

获取 Spring 通讯

订阅 Spring 通讯,保持联系

订阅

抢占先机

VMware 提供培训和认证,助您加速前进。

了解更多

获取支持

Tanzu Spring 通过简单的单一订阅提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将到来的活动

查看 Spring 社区所有即将到来的活动。

查看全部