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 流

Java 8 的一项重要新功能是Stream API,它允许 Java 开发人员定义要在对象流上执行的操作管道,但只有最终操作才会真正触发从Stream中使用元素。

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

在 Fowler 版本中,我们引入了对 Java 8 流作为存储库中查找器方法的返回类型的支持。在 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 示例存储库中找到一个完整的示例。请注意,由于转换器只是将 JSR-310 类型转换为旧的Date实例,因此仅支持非时区(例如LocalDateTime等)。

MongoDB

3.0 服务器和驱动程序支持

Spring Data Fowler 提供了对最新最好的 MongoDB 3.0 服务器的支持。虽然可以使用版本 2.13.0 的 MongoDB Java 驱动程序使用该版本,但我们也确保 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://127.0.0.1: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://json-schema.fullstack.org.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 Schema 文档中显示为 format 设置为 email

Solr

文档评分改进

Fowler 版本添加了对实时获取的支持,允许从索引中读取未提交的更改。getById 可用于 SolrTemplate。同样值得注意的是添加了 @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 发布的所有新功能的表面。您可能想探索发布列车维基以获取更多信息,并依次遍历指向工单和相关提交的链接,因为它们包含通常很好地演示单个功能的测试用例。

此外,前面提到的Spring Data 示例存储库有很多内容可供您使用和探索。

获取 Spring 新闻通讯

与 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获取支持

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

了解更多

即将举行的活动

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

查看全部