Spring Data Fowler 有哪些新功能?

工程 | Thomas Darimont | 2015 年 3 月 26 日 | ...

Spring Data 发布列车 Fowler 的 GA 版发布标志着 6 个月开发的终点。现在是时候让您了解此次发布的内容并简要概述各项功能了。Fowler 发布列车的主要主题是性能改进和增强的 Java 8 支持,这主要体现在 Spring Data JPA 和 MongoDB 模块中,但许多其他模块也得到了显著改进。

升级到 Spring Data Fowler 版本系列最简单的方法是使用 Spring Boot 并将 spring-data-releasetrain.version 属性配置为 Fowler-RELEASE。如果您尚未开始使用 Spring Boot,请将 Spring Data BOM 添加到您的 Maven POM 的 <dependencyManagement /> 部分。

通用主题

存储库方法中的 Java 8 Stream

Java 8 的一个重要新特性是 Stream API,它允许 Java 开发人员定义一个操作管道,对对象流执行操作,但只有最终的操作才会实际触发对 Stream 中元素的消费。

在数据访问的上下文中,将查询执行的结果作为 Stream 提供是一个非常有用的用例,因为它可以防止查询方法的调用者在所有项都读取完毕之前阻塞。更不用说这里更高效的内存使用。

在 Fowler 版本中,我们引入了对 Java 8 Stream 作为存储库中查找方法返回类型的支持。在 MongoDB 和 JPA 模块中,您现在可以这样声明查找方法

interface CustomerRepository extends Repository<Customer, Long> {

  Stream<Customer> findByLastname(String lastname);
}

调用此方法将执行支持存储库方法的查询,并在第一个结果可用时立即返回。为了在 JPA 中实现这一点,我们使用持久性提供程序特定的 API,因为 JPA 本身只提供以 List 形式获取查询结果的方法。存储库客户端现在可以在 try-with-resources 块中使用方法调用的结果。这将确保为遍历流而打开的资源最终会被关闭。

try (Stream<Customer> customers = repository.findByLastname("Matthews")) {
  customers.filter(…).map(…).collect(…);
}

JSR-310 和 ThreeTen Backport 支持

为了轻松持久化领域对象中的 无时区 JSR-310 类型——JDK 8 中新引入的日期/时间 API,我们在 MongoDB 和 JPA 模块中为相关类型添加了转换器。对于尚不能升级到 Java 8 的开发人员,我们为 ThreeTen Backport 项目 添加了类似的转换器集,这样即使您仍在使用 Java 7,也可以在代码中开始使用这些类型。未来切换到 Java 8 将是包名称的简单切换。

在 MongoDB 模块中,相应的 Converter 实现是自动可用的。对于 JPA,您只需向持久性提供程序注册 Jsr310JpaConvertersThreeTenBackPortJpaConverters。如果您使用 LocalContainerEntityManagerFactoryBean 在 Spring 中设置 JPA 环境,只需将 org.springframework.data.jpa.convert.threeten….threetenbp 添加到要扫描的包中。对于 Spring Boot,只需将上述类添加到 @EntityScan 声明中即可

@EntityScan(
  basePackageClasses = { Application.class, Jsr310JpaConverters.class }
)
@SpringBootApplication
class Application { … }

此设置将确保您的应用程序包和 Spring Data JPA 包(用于 JSR-310 转换器)都将被扫描并传递给持久性提供程序。有关完整示例,请参阅我们的 Spring Data Examples 存储库。请注意,由于转换器只是将 JSR-310 类型转换为旧版 Date 实例,因此 只支持无时区 类型(例如 LocalDateTime 等)。

MongoDB

3.0 服务器和驱动程序支持

Spring Data Fowler 支持最新最强大的 MongoDB 3.0 服务器版本。虽然该版本已经可以通过 MongoDB Java 驱动程序 2.13.0 版本使用,但我们也确保 Spring Data MongoDB 将与即将推出的 Java 驱动程序 3.0 版本无缝协作。因此,开发人员可以自由选择他们要使用的版本,或者何时升级到新驱动程序。有关驱动程序和服务器版本之间兼容性的通用信息,请务必查阅 MongoDB 文档。但请注意,后续开发将明确关注服务器和驱动程序的 3.0 版本线。

通常,我们鼓励每个人在 JavaConfig 中优先使用 MongoClient 而不是 Mongo,或者使用新引入的 XML 元素 <mongo:mongo-client /><mongo:client-options />。有关更多信息,请参阅参考文档

GeoJSON 支持

MongoDB 引入 GeoJSON 作为处理地理结构格式已经有一段时间了。这些数据结构在类似地球的球体上操作,因此不能与 2D 索引一起使用。话虽如此,使用起来非常简单,因为我们提供了专门的类型来支持 GeoJSON。这些类型既可以在您的域类型中使用,也可以作为查询参数使用。

@Document
class Store {

  @Id String id;

  /**
   * The location is stored in GeoJSON format:
   * { "type" : "Point", "coordinates" : [ x, y ] }
   */
  GeoJsonPoint location;
}

interface StoreRepository extends CrudRepository<Store, String> {

  List<Store> findByLocationWithin(Polygon polygon);
}

repo.findByLocationWithin(
  new GeoJsonPolygon(
    new Point(-73.992514, 40.758934),
    new Point(-73.961138, 40.760348),
    new Point(-73.991658, 40.730006),
    new Point(-73.992514, 40.758934)));

这将在 MongoDB 中创建以下查询并执行

{
  "location": {
    "$geoWithin": {
      "$geometry": {
        "type": "Polygon",
        "coordinates": [[
           [-73.992514,40.758934],
           [-73.961138,40.760348],
           [-73.991658,40.730006],
           [-73.992514,40.758934]
        ]]
      }
    }
  }
}

请注意,StoreRepository.findByLocationWithin(…) 仍然接受 Polygon。使用 GeoJsonPolygonfindByLocationWithin(…) 将创建使用 $geometry 操作符以及 GeoJSON 表示的查询。有关使用和限制的更多详细信息,请参阅 MongoDB 手册

MongoDB 存储脚本的执行

MongoDB 允许在服务器上执行 JavaScript 函数,方法是直接发送原始源脚本或调用先前存储的脚本。我们通过新引入的 ScriptOperations 接口公开此功能,该接口可以从 MongoOperations 获取。

ScriptOperations ops = mongoOperations.scriptOps();
ExecutableMongoScript script = new ExecutableMongoScript("function(x) { return x; }");
Object r1 = ops.execute(script, "Direct function execution.")

ops.register(new NamedMongoScript("echo", script));
Object r2 = ops.call("echo", "Call stored function.");

服务器端脚本支持将在后续版本中得到增强,我们将添加返回类型转换、用于从存储库方法调用过程的注解以及对 $where 运算符的支持。

对象到存储转换的性能改进

对象到存储的映射子系统在性能改进方面进行了重大改革。我们对 Commons 和存储模块进行了分析,并在各处引入了一些缓存,与 Evans 版本系列相比,我们实际获得了令人印象深刻的每秒操作次数的增长(尽管大部分改进也已移植到 Evans 的服务版本中)。

Performance improvements in Spring Data Fowler

如您所见,我们的读取访问每秒操作数增加了一倍以上,写入操作也接近此水平。

从数据存储中读取大量对象时,通过反射创建对象实例会花费大量时间。在 Fowler 发布系列中,我们引入了一个新的默认 EntityInstantiator,它通过使用 ASM 在运行时为域对象创建工厂类来解决这一瓶颈。该工厂类直接调用域类的构造函数,这比通过反射这样做要快得多。如果您对这些精巧的细节感兴趣,这里是为我们完成这项工作的类。

Redis

HyperLogLog

Redis HyperLogLog 命令提供了一种高效的解决方案,可以在不记住已遇到元素的情况下计算唯一事物。例如,这适用于按 IP 地址计算唯一页面访问量。

HyperLogLogOperations hll = redisTemplate.opsForHyperLogLog();

hll.add(today(), "8.8.8.8", "8.8.4.4");
hll.size(today()); // Unique page visits today = 2

hll.add(today(), "198.153.192.40", "8.8.8.8");
hll.size(today()); // Unique page visits today = 3
hll.size(today(), yesterday()); // Unique page visits today and yesterday

Gemfire

到目前为止,GemFire 模块最显著的变化是全面支持 GemFire 8。GemFire 8 自 7.0.2 以来引入了多项新变化,包括新的基于集群的配置服务

启用该服务后,开发人员可以在 Gfsh 中记录他们的操作和类似模式的更改,例如添加区域、创建索引、配置磁盘存储等。当开发人员在集群中启动新的 GemFire 对等节点时,该成员将自动从新的基于集群的配置服务(托管在定位器中)获取其配置。

虽然 Spring Data GemFire 的基于 XML 命名空间的配置仍然是流行的选择,尤其是在高度迭代、短反馈周期的开发过程中,Spring Data GemFire 增加了对新基于集群的配置的支持,这与 Spring Data Gemfire 对 GemFire 的原生 `cache.xml 格式的支持行为类似。

要在 Spring 配置的 GemFire 节点中启用基于集群的配置,开发人员只需在 <gfe:cache /> 元素上设置 use-cluster-configuration 属性,如下所示

<gfe:cache id="gemfireCache" use-cluster-configuration="true" … />

Spring Data GemFire 将首先请求并应用集群范围的配置,然后应用 XML 命名空间特定的配置。您可以将 XML 配置元数据视为对集群配置服务发送的集群配置的增强。

有关 GemFire 新集群配置服务的更多信息,请参阅 GemFire 用户指南SGF-226 以获取更多详细信息。

Spring Data REST

Spring Data REST 在 Fowler 版本中也进行了广泛的改进。最显著的改进之一是检查和使用更多实体元数据来填充响应头。例如,支持通过 @Version 注解进行乐观锁定的存储现在将获取用作 ETag 头的实体版本,以便客户端可以利用它们触发条件 GET 请求。

与此密切相关的是,使用 Spring Data 审计支持的实体将自动将其上次修改日期传播到项目资源的响应的 LastModified 头中

class Customer {

  @Version Long version;
  @LastModifiedDate LocalDate lastModifiedDate;
}
curl -v http://…/customers/1

Etag: 1
Last-Modified: Tue, 24 Mar 2015 12:34:56 GMT

JSON Schema

Spring Data REST 的 Fowler 版本也提供了经过改进的 JSON Schema 支持。默认情况下,模式由 ALPS 文档中公开的表示描述符指向,该文档用于域类型。在 Starbucks 示例中,您可以看到链接的渲染方式如下

curl http://…/alps/stores

{
  "version": "1.0",
  "descriptors": [ {
    "id": "store-representation",
    "href": "https://:8080/stores/schema",
    "descriptors": [ … ]
  }],
  …
}

点击链接将显示商店的 JSON Schema 文档

{
  "title": "example.stores.Store",
  "properties": {
    "address": {
      "$ref": "#/descriptors/address"
    },
    "name": {
      "type": "string"
    }
  },
  "descriptors": {
    "address": {
      "type": "object",
      "properties": {
        "zip": {
          "type": "string"
        },
        "city": {
          "type": "string"
        },
        "street": {
          "type": "string"
        },
        "location": {
          "$ref": "#/descriptors/point"
        }
      }
    },
    "point": {
      "type": "object",
      "properties": {
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        }
      }
    }
  },
  "type": "object",
  "$schema": "https://schema.json.js.cn/draft-04/schema#"
}

请注意我们如何从领域类型及其 Jackson 映射中推导出模式的基本特征。所需属性可以通过使用 @JsonProperty(required = true) 来确定,日期/时间类型被正确发现并声明。您可以在 RepositoryRestConfiguration.metadataConfiguration() 上注册自定义 JSON Schema 格式或模式。

@Configuration
static class SampleConfiguration extends RepositoryRestMvcConfiguration {

  @Override
  protected void configureRepositoryRestConfiguration(
    RepositoryRestConfiguration config) {
    config.metadataConfiguration().
      registerJsonSchemaFormat(JsonSchemaFormat.EMAIL, EmailAddress.class);
  }
}

假设 EmailAddress 是一个您已调整 Jackson 以将其渲染为纯 String 的值对象,则此配置将导致所有 EmailAddress 类型的属性在 JSON 模式文档中以 format 设置为 email 出现。

Solr

文档评分改进

Fowler 版本增加了对实时获取的支持,允许从索引中读取未提交的更改。SolrTemplate 上提供了 getById 方法。另外值得注意的是添加了 @Score,它受到了 Spring Data MongoDB 可用的 @TextScore 的启发。该注解允许检索文档评分,并将隐式添加所需参数,因此在检索文档查询匹配评分时不再需要显式添加 @Query(fields={"*", "score"}

杂项

提升投影

Spring Data REST 在 Evans 版本系列中发布了一个名为投影的功能。在 Fowler 版本中,我们将支持该功能的基础设施移至 Spring Data Commons,并对其进行了一些调整,以便其他项目可以在没有额外依赖的情况下使用它。该功能的核心是 ProjectionFactory,它允许您为由其他对象(例如 Map)支持的接口创建对象实例。

interface Customer {

  String getFirstname();

  String getLastname();

  @Value("#{target.firstname + ' ' + target.lastname}")
  String getFullName();
}

现在可以使用 ProjectionFactory 将此接口转换为对象。

Map<String, Object> map = new HashMap<>();
map.put("firstname", "Dave");
map.put("lastname", "Matthews");

ProjectionFactory factory = new SpelAwareProjectionFactory();
Customer customer = factory.createProjection(Customer.class, map)

assertThat(customer.getFirstname(), is("Dave"));
assertThat(customer.getLastname(), is("Matthews"));
assertThat(customer.getFullName(), is("Dave Matthews"));

如您所见,我们选择了一个 Map 来支持创建的投影实例。在底层,创建了一个 JDK 代理,它配备了方法拦截器,在 Map 支持代理的情况下,它将对访问器的调用委托给 Map 中的属性查找。使用 @Value 注解的方法将对其注解的 SpEL 表达式进行求值。如果您在 SpelAwareProjectionFactory 上配置 BeanFactory,您甚至可以在这些表达式中引用 Spring bean,从而触发更复杂的计算。

如果后台查找未返回可分配给声明的返回类型的值,则会使用标准 ConversionService 进行简单转换,然后进行递归投影步骤。

有关如何在 Spring MVC 控制器中使用投影的示例,请参阅 StackOverflow 上的此答案

Spring MVC 中的投影

投影机制现在可以被 Spring MVC 控制器实现使用,以仅使用接口创建表单支持对象。在您的 Spring 配置中使用 @EnableSpringDataWebSupport(在 Boot 中自动激活)将解析 ProxyingHandlerMethodArgumentResolver,它将自动为接口创建代理实例并将相应的请求参数绑定到它。

interface Form {

  @NotBlank String getName();
  @NotBlank String getText();
}

@Controller
@RequestMapping(value = "/guestbook")
class GuestbookController {

  @RequestMapping(method = RequestMethod.GET)
  String guestbook(Form form, Model model) { … }

  @RequestMapping(method = RequestMethod.POST)
  String guestbook(@Valid Form form, Errors errors, Model model) { … }
}

请看接口如何在接受 GET 请求的方法中使用,以便为即将渲染的视图提供一个空的表单支持对象。接收 POST 请求的方法使用 Form 来指示它希望将表单数据绑定到代理实例并应用验证。

总结

尽管这篇文章很长,但我们只是简单介绍了 Spring Data Fowler 版本的所有新功能。您可能需要浏览 发行版系列 wiki 以寻找更多精彩内容,并依次遍历指向票据和相关提交的链接,因为它们包含通常很好地演示各个功能的测试用例。

此外,前面提到的 Spring Data 示例仓库 也包含大量可供您尝试和探索的内容。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获得支持

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

了解更多

即将举行的活动

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

查看所有