Spring Data Gosling 版本有哪些新功能?

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

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

即时 JPA Fetch Graph。

自 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 支持向前推进了一步,将其扩展到即时 fetch graph 定义。通过在查询方法上使用 @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 browser](https://gist.githubusercontent.com/olivergierke/4d3683c12769211d97cb/raw/9d11209ea9840b42693854ba62f8a46bc9940f39/hal-browser.png)

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

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

Spring Data REST 利用 profile 链接关系公开系统中暴露类型的 JSON Schema 文档,这使得 Schema 可以通用地发现,而无需将发现逻辑绑定到 Spring Data REST 本身。我们随附的浏览器实例将查找该 Schema 元数据,如果找到,则将其交给 JSON Editor,用完全基于 JSON Schema 生成的表单替换默认对话框。

![JSON Editor based POST form](https://gist.githubusercontent.com/olivergierke/4d3683c12769211d97cb/raw/9d11209ea9840b42693854ba62f8a46bc9940f39/json-editor-post-form.png)

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

请看表单如何允许添加明细项,因为 Schema 将其暴露为一个数组。价格和订单日期字段被标记为只读,位置字段允许从具有国际化值的枚举中选择值。

示例项目可以在 GitHub 上找到。

正如您在上面的截图中看到的那样,restbucks:orders 链接伴随有一个人类可读的描述。这些描述从可选的资源包 rest-messages 中提取,使用 _links.$rel.title 键来定义可读的值。示例使用 rest-messages.properties 作为备用资源包,但也包含一个 rest-messages_de.properties,用于向发送设置为 deAccept-Language 头的客户端返回德语标签。

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

Spring Data GemFire & Apache Geode

Spring Data GemFire 1.7 最值得注意的新增功能是对 Pivotal GemFire 8.1 和 Apache Geode 的支持。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 区域上为 TTL、TTI 或两者配置 Spring Data GemFire 的 CustomExpiry 实现 AnnotationBasedExpiration

<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 Expression Language (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 在 2015 年有哪些新功能?”演讲中偷偷透露 Hopper 的一些内容)。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获取支持

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

了解更多

即将举行的活动

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

查看全部