Spring Data Gosling 版本的新特性

工程 | Christoph Strobl | 2015年9月4日 | ...

在 12 个项目中修复了 300 多个问题,这使得很难跟踪自上次发布以来发生了什么。因此,这里更详细地摘录了我们在上次迭代中开发的一些新功能。

JPA 即席获取图。

自从 Dijkstra 发布以来,我们能够通过 JPA 支持的存储库中的 @EntityGraph 注解引用实体上声明的命名实体图。在下面的示例中,这强制 firstname 和 lastname 提前加载,而所有其他属性保持延迟加载。

@Entity
@NamedEntityGraphs(
  @NamedEntityGraph(name = "with-tags",
    attributeNodes = { @NamedAttributeNode("tags") }))
class Product {

  @ManyToMany
  Set<Tag> tags;

  // other properties omitted
}

interface ProductRepository extends Repository<Customer, Long> {

  @EntityGraph("with-tags")
  Product findOneById(Long id);
}

Gosling 版本现在将我们的 JPA 2.1 故事向前推进了一步,将其扩展到即席获取图定义。通过在查询方法上通过 @EntityGraph(attributePaths = …) 显式指定属性,您无需在实体上使用 NamedEntityGraph 注解。

@Entity
class Product {

  @ManyToMany
  Set<Tag> tags;

  // other properties omitted
}

interface ProductRepository extends Repository<Customer, Long> {

  @EntityGraph(attributePaths = {"tags"})
  Product findOneById(Long id);
}

Querydsl Web 支持

Spring Data Web 支持已经允许您在控制器处理程序方法中声明 Pageable 类型的参数。新引入的 Querydsl 集成将此扩展到允许您接收直接从 HTTP 请求的查询字符串派生的可立即使用的 Predicate。当配置 @EnableSpringDataWebSupport 并且在类路径上找到 Querydsl 时,此功能会自动启用。

如果 Predicate 在没有进一步配置的情况下使用,我们将尝试从方法的返回类型解析 Predicate 解析的根类型,尽管在大多数情况下,最好通过 @QuerydslPredicate(root = …) 显式声明所需的类型引用。有了它,查询字符串属性会绑定到类型的匹配属性,例如:

QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews")) 

?firstname=Dave&lastname=Matthews 使用默认的属性类型相关绑定。

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

  @Controller
  @RequiredArgsConstructor(onConstructor = @__(@Autowired))
  static class UserController {

    private final UserRepository repository;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    String index(Model model,
                 @QuerydslPredicate(root = User.class) Predicate predicate,
                 Pageable pageable) {

      model.addAttribute("users", repository.findAll(predicate, pageable));
      return "index";
    }
  }
}

现在,使用默认(等于)绑定并不总是合理,而是每个属性或特定类型的专用绑定。要实现此目的,只需通过提供 QuerydslBinderCustomizer 覆盖默认值,该自定义器可以通过 @QuerydslPredicate(bindings = …) 注册,或者由存储库简单实现。

interface UserRepository extends CrudRepository<User, String>,
    QueryDslPredicateExecutor<User>,
    QuerydslBinderCustomizer<QUser> {

  // Query methods go here

  @Override
  default public void customize(QuerydslBindings bindings, QUser user) {

    bindings.bind(user.nationality).first(
      (path, value) -> path.equalsIgnoreCase(value)); // 1
    bindings.bind(String.class).first(
      (StringPath path, String value) -> path.containsIgnoreCase(value)); // 2
    bindings.excluding(user.password);
  }
}

如您所见,我们利用 Java 8 lambda 与 Querydsl 的类型安全属性引用一起定义专用属性 (1) 或给定类型的所有属性 (2) 的绑定。使用 QuerydslBindings.excluding 允许您删除无法查询的路径。

Spring Data 示例存储库 中找到一个完整的可工作示例,并查看 参考文档 以获取详细信息。

Spring Data REST

Querydsl 支持

在 Spring Data Commons 中引入的 Querydsl 支持(参见 上文)已集成到 Spring Data REST 中。这意味着您可以通过将简单的属性查询参数附加到请求 URI 来过滤集合资源。

在公开星巴克商店位置的 Spring Data REST 示例中,请注意 StoreRepository 如何同时实现 QueryDslPredicateExecutorQuerydslBinderCustomizer<QStore>,就像上面描述的那样。

然后,Spring Data REST 公开的商店的集合资源将允许您发出如下请求

$ http :8080/api/stores?address.city=York

{
    "_embedded": {
        "stores": [
            {
                "_links": {
                    …
                }, 
                "address": {
                    "city": "New York", 
                    "location": { "x": -73.938421, "y": 40.851 }, 
                    "street": "803 W 181st St", 
                    "zip": "10033-4516"
                }, 
                "name": "Washington Hgts/181st St"
            }, 
            {
                "_links": {
                    …
                }, 
                "address": {
                    "city": "New York", 
                    "location": { "x": -73.939822, "y": 40.84135 }, 
                    "street": "4001 Broadway", 
                    "zip": "10032-1508"
                }, 
                "name": "168th & Broadway"
            }, 
            …
        ]
    }, 
    "_links": {
        …
    }, 
    "page": {
        "number": 0, 
        "size": 20, 
        "totalElements": 209, 
        "totalPages": 11
    }
}

请注意,如何仅返回城市以“York”结尾的商店,就像在 StoresRepositoryQuerydslBinderCustomizer 的实现中定义的那样。

我们目前正在研究更明显地宣传这种查询机制的选项,例如使用模板变量,甚至提供高级映射功能来自定义要使用的请求参数名称。

自定义 HAL 浏览器

Spring Data REST 的 Gosling 版本附带一个额外的模块,该模块包装了 Mike Kelly 的 HAL 浏览器 并对其进行了一些自定义调整以利用我们公开的 API 元数据。要在您的应用程序中使用浏览器,只需将 spring-data-rest-hal-browser 模块添加到您的项目中,您的 API 根目录将为接受 text/html 的请求提供服务。当然,标准 HAL 响应仍然默认提供,或者如果您使用基于 JSON 的 Accept 标头。

![HAL 浏览器](https://gist.githubusercontent.com/olivergierke/4d3683c12769211d97cb/raw/9d11209ea9840b42693854ba62f8a46bc9940f39/hal-browser.png)

图 1. - HAL 浏览器(点击放大)

虽然 Spring Data REST 模块使将浏览器添加到您的应用程序变得很容易,但它也稍微调整了浏览器。当您单击按钮触发非 GET 请求时,浏览器通常会打开一个模态对话框,该对话框需要一些原始 JSON 输入。虽然如果您知道自己在做什么,这当然很好,但它有点容易出错,而且不是很方便,因为您必须了解服务器期望的数据结构。

Spring Data REST 公开系统公开的类型的 JSON 模式文档,利用 profile 链接关系,这使得模式可以通用地发现,而无需将发现逻辑绑定到 Spring Data REST 本身。我们提供的浏览器实例将查找该模式元数据,并且 - 如果它能够找到一些元数据 - 将其交给 JSON 编辑器,以使用完全从 JSON 模式派生的表单替换默认对话框。

![基于 JSON 编辑器的 POST 表单](https://gist.githubusercontent.com/olivergierke/4d3683c12769211d97cb/raw/9d11209ea9840b42693854ba62f8a46bc9940f39/json-editor-post-form.png)

图 2. - 基于 JSON 编辑器的 POST 表单(点击放大)

请查看表单如何允许添加行项目,因为模式将其公开为数组。价格和订单日期字段被标记为只读,位置字段允许从具有国际化值的枚举中选择值。

示例项目可以在 GitHub 上找到

如您在上面的屏幕截图中看到的,restbucks:orders 链接附带了一个人类可读的描述。描述是从可选资源包 rest-messages 中提取的,使用 _links.$rel.title 键定义可读值。示例使用 rest-messages.properties 作为后备资源包,但也包含 rest-messages_de.properties 以针对将 Accept-Language 标头设置为 de 的客户端返回德语标签。

相同的资源包可用于国际化枚举值,以便它们可以在客户端以人类可读的方式使用。为了不破坏现有应用程序,必须通过 RepositoryRestConfiguration.setEnableEnumTranslation(…) 显式激活此功能。有关翻译的详细信息,可以在 EnumTranslationConfiguration 上配置。

Spring Data GemFire 和 Apache Geode

对 Pivotal GemFire 8.1 和 Apache Geode 的支持是 Spring Data GemFire 1.7 最显著的补充。Pivotal GemFire 今年早些时候提交给了 Apache Incubator,Spring Data 团队迅速做出反应,在 Spring Data GemFire 中包含了 支持

此外,还添加了一些其他功能来简化使用 Spring 开发 GemFire 和 Apache Geode 应用程序的过程。例如,开发人员现在可以使用注解定义特定于应用程序域对象的过期策略

@TimeToLiveExpiration(
  timeout = "@expirationSettings['spel.defined.timeout']" action="DESTROY")
@IdleTimeoutExpiration(
  timeout = "1800" action="${property.placeholder.defined.action}")
class ApplicationDomainObject { … }

基于过期的注解支持 SpEL 和 Spring 属性占位符 值。要启用基于注解的过期策略,您只需要在您的 GemFire 区域上配置 Spring Data GemFire 的 CustomExpiry 实现 AnnotationBasedExpiration,用于 TTL 和 TTI 中的一个或两个。

<gfe:partitioned-region id="Example" persistent="false" …>
  <gfe:custom-entry-ttl>
    <bean class="….gemfire.support.AnnotationBasedExpiration" factory-method="forTimeToLive"/>
  </gfe:custom-entry-ttl>
  <gfe:custom-entry-tti ref="ttiExpiration"/>
</gfe:partitioned-region>

<bean id="ttiExpiration" class="….gemfire.support.AnnotationBasedExpiration" factory-method="forIdleTimeout">
  <constructor-arg ref="defaultExpirationAttributes"/>
</bean>

<bean id="defaultExpirationAttributes" class="….ExpirationAttributes">
  <constructor-arg value="600"/>
  <constructor-arg value="#{T(….ExpirationAction).DESTROY}"/>
</bean>

请参阅参考指南以 了解更多信息。接下来,为存储库查询方法 OQL 扩展添加了基于注解的支持

interface CustomerRepository implements CrudRepository<Cutomer, Long> {

  @Trace
  @Limit(25)
  @Import("org.exmple.Customer")
  @Hint("CustomerLastNameIdx")
  List<Customer> findByLastNameOrderByLastNameAsc(String lastName);
}

@Trace 用于启用单个 OQL 语句的调试。@Limit 用于限制查询结果集中的结果数量,而@Import 则允许应用程序区分名称相似的对象类型。例如,您的应用程序可能会同时定义org.example.app.core.Customerorg.example.app.vendor.xyz.Customer 类型。有关更多详细信息,请参阅 GemFire 的文档@Hint 允许使用 OQL 提示来识别适用于查询的索引。在此处了解有关 OQL 扩展的更多信息此处

最后,Spring Data GemFire 使用 Spring Data GemFire XML 数据命名空间提供对 GemFire 缓存和区域快照 的支持。

<gfe:partitioned-region id="Example" persistent="false" … />

<gfe-data:snapshot-service id="exampleRegionSnapshotService" region-ref="Example">
  <gfe-data:snapshot-import location="/path/to/import/example.snapshot"/>
  <gfe-data:snapshot-export locator="/path/to/export/example.snapshot"/>
</gfe-data:snapshot-service>

您可以在此处了解有关 Spring Data GemFire 如何支持导入时的 ZIP 文件、使用 Spring ApplicationEvents 触发导入和导出快照以及如何适当地过滤导入和导出的数据的更多信息此处

Spring Data KeyValue & 基于 Map 的存储库

我们被要求为 Spring Data 存储库提供非常简单的基于Map 的实现已经有一段时间了,这些请求主要用于测试目的。这些请求最终导致以与之前略有不同的方式恢复了KeyValue 模块

Spring Data KeyValue 现在包含一个基于Map 的基本存储库实现,它将默认使用 Spring 表达式语言 (SpEL) 来查询值,并提供基于其集合模块的排序、分页和 Querydsl 集成。它还公开了专用的 API,允许键值存储在需要时利用存储特定优化来进行存储、检索和最重要的是查询执行。

Spring Data KeyValue 存储库使用的默认查询机制基于 SpEL,允许您定义和运行复杂的查询。当在COMPILED 模式下运行时,这种方法显示了其真正的强大功能,因为它有效地编译了要在值上执行的过滤器表达式。或者,您也可以使用 Querydsl 表达式进行类型安全的查询。

@Configuration
@EnableMapRepositories("com.acme.repositories")
class AppConfig {}

@KeySpace("user")
class User {

  String @Id id;
  String firstname;
}

interface UserRepository extends CrudRepository<User, String> {
  List<String> findByFirstnameStartingWith(String firstname);
}

我们目前正在为 Ehcache、HazelcastAerospike 开发该 API 的扩展,并且期待评估也集成 Redis 以及可能移植一些 Gemfire API 以使用它的选项。

接下来是什么?

接下来是华盛顿特区的 SpringOne2GX - 我们很乐意在那里见到您 - 与团队联系、了解新功能并享受美好时光的最佳场所。同时,我们已经在为 Fowler 发布列车准备下一个服务版本,并开始为 Hopper 发布列车开发新功能(嘘... 我们将在 SpringOne 的“Spring Data 的新功能?”演讲中提供 Hopper 的抢先预览)。

获取 Spring 时事通讯

与 Spring 时事通讯保持联系

订阅

抢先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

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

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部