Spring Data 2021.0 的新特性

工程 | Mark Paluch | 2021年4月21日 | ...

Spring Data 2021.0(代号 Pascal)是在新的六个月发布周期后的第二个版本。它包含了许多现有接口和编程模型的改进。这篇博文解释了以下主题:

CrudRepositoryReactiveCrudRepository 引入 deleteAllById 方法

从一开始,CrudRepository 就定义了一个方法,用于通过其标识符删除单个实体。在 1.x 开发分支中,delete(…) 方法被重载以接受各种参数类型,遵循 delete(ID id)delete(Iterable<? extends T> entities)

随着 Spring Data 2.0 的发布,我们重命名了 CrudRepository 的方法,以表达每个方法接受的参数。重命名后,方法看起来像 deleteById(ID id)deleteAll(Iterable<? extends T> entities)。改进的命名约定为引入一个通过标识符删除实体的删除方法留出了空间。从本版本开始,CrudRepositoryReactiveCrudRepository 都定义了 deleteAllById(Iterable<? extends ID> ids) 来删除多个实体。

根据实际的存储模块,如果数据存储支持,这可能是一个批量删除(通过查询删除)。例如,JPA 实现仍然首先具体化所有实体以立即删除它们,以便在即将删除的实例上仍然调用生命周期回调。JpaRepoository 中还引入了额外的 deleteAllByIdInBatch(…) 以使用批处理查询提供更快的执行变体。

支持 Spring Core Java Flight Recorder (JFR) 指标

Java Flight Recorder (JFR) 是一种用于收集、诊断和分析正在运行的 Java 应用程序的数据的工具。它与 Java 运行时的紧密集成允许在生产环境中以低开销收集事件。

Spring Data 存储库通常在应用程序启动时引导,因此它们自然会影响启动时间。Pascal 版本引入了与 Spring Framework 支持捕获 启动事件 的集成,该集成自 5.3 版本起可用。通过启用 JFR 记录,您可以收集和分析以下存储库启动事件:

对于每个启用的 Spring Data 模块(@Enable…Repositories

  • spring.data.repository.scanning:存储库接口扫描

对于每个存储库

  • spring.data.repository.init:存储库初始化

  • spring.data.repository.metadata:元数据检索

  • spring.data.repository.composition:存储库组合的组装

  • spring.data.repository.target:存储库目标创建

  • spring.data.repository.proxy:存储库代理创建

  • spring.data.repository.postprocessors:存储库代理后处理

您可以在所有 Java 9 或更高版本的运行时或 Java 8 更新 262 或更高版本上使用 java -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar … 启动应用程序来启用 JFR 记录。

类型安全和重构安全的 KPropertyKPropertyPath 用于属性路径渲染

Spring Data Kotlin 集成是我们的特定语言扩展的“语法糖”增强功能的有力驱动力。Kotlin 允许将单个属性引用为属性引用(data class Book(val title: String)Book::title)。它们是重构安全和编译安全的,因为 Kotlin 编译器会立即拒绝无效的引用。现代 IDE 支持在重命名属性时会考虑属性引用,从而消除了在普通字符串中存在持久引用的风险。

Spring Data MongoDB 2.2 为其 Criteria API 引入了对 KPropertyKPropertyPath 的支持。

属性的经典用法

val classic = Criteria("title").isEqualTo("Moby-Dick")
  .and("price").lt(950)

val typed = (Book::title isEqualTo "Moby-Dick")
  .and(Book::price).lt(950)

Spring Data Commons 2.5 将 KPropertyPath 提升为 Spring Data 中的概念。为了不需要扩展或更改所有接受属性路径的方法,您可以通过渲染属性路径将 KPropertyPath 与现有的 Spring Data 实用程序一起使用。

// KPropertyPath variant
Sort.by((Book::author / Author::name).toDotPath())

// String-path equivalent
Sort.by("author.name")

// KPropertyPath variant
ExampleMatcher.matching()
  .withMatcher((Book::author / Author::name).toDotPath(), contains())

// String-path equivalent
ExampleMatcher.matching()
  .withMatcher("author.name", contains())

从发布列车中移除 Spring Data for Apache Solr

此发布列车不再附带 Spring Data for Apache Solr。在 2020 年弃用 Spring Data Solr 之后,团队已决定停止维护 Solr 模块。但是,我们将继续为维护的 4.2 和 4.3 开发分支发布服务版本,直到它们分别在 2021 年 5 月和 2021 年 11 月达到使用寿命。展望未来,我们建议使用 Spring Data Elasticsearch 作为用于全文搜索安排的首选 Spring Data 模块。Spring Data Elasticsearch 是一个积极维护的社区模块。

R2DBC 和 Oracle 支持 QueryByExample

示例查询 是一种用户友好的查询技术,具有简单的界面。它允许动态创建查询,并且不需要编写包含字段名称的查询。实际上,示例查询根本不需要您使用 SQL 编写查询。它可用于多个 Spring Data 模块。从 Spring Data R2DBC 1.3 开始,您可以使用 Spring Data R2DBC 的 ReactiveQueryByExampleExecutor 实现通过示例查询关系数据。

PersonRepository people  = …;
DatabaseClient client = …;

var skyler = new Person(null, "Skyler", "White", 45);
var walter = new Person(null, "Walter", "White", 50);
var flynn = new Person(null, "Walter Jr. (Flynn)", "White", 17);
var marie = new Person(null, "Marie", "Schrader", 38);
var hank = new Person(null, "Hank", "Schrader", 43);

var example = Example.of(new Person(null, null, "White", null));

people.count(example).as(StepVerifier::create)
  .expectNext(3L)
  .verifyComplete();


var example = Example.of(new Person(null, "Walter", "WHITE", null), matching()
  .withIgnorePaths("age"). //
  .withMatcher("firstname", startsWith())
  .withMatcher("lastname", ignoreCase()));

people.findAll(example).collectList()
  .as(StepVerifier::create)
  .consumeNextWith(actual -> {
    assertThat(actual).containsExactlyInAnyOrder(flynn, walter);
  })
  .verifyComplete();

除了其他改进之外,您还可以将 Spring Framework 5.3.6 和 Spring Data R2DBC 1.3 与 Oracle 的 oracle-r2dbccom.oracle.database.r2dbc:oracle-r2dbc)驱动程序一起使用。使用 Oracle ConnectionFactory 创建 DatabaseClientR2dbcEntityTemplate 会选择合适的绑定标记策略和方言。

为存储库和CassandraTemplate启用 Cassandra 预准备语句

Spring Data for Apache Cassandra 尽可能地适应 Cassandra 的特定功能。自从 2.0 版本进行重大重写以来,我们引入了CachedPreparedStatementCreator,用于在CqlTemplate级别缓存预准备语句,这允许使用纯 CQL 使用预准备语句。

在此版本中,我们在CassandraTemplate及其反应式和异步变体中引入了预准备语句支持。实际上,默认情况下启用了预准备语句。CqlTemplateCassandraTemplate之间的主要区别在于抽象级别和 CQL 语句创建的职责。CqlTemplate需要 CQL 作为输入。CassandraTemplate使用实体作为输入,并根据使用实体应执行的实际操作生成 CQL 语句。

提供预准备语句功能的更改在发出查询时会带来一些更改

  1. 使用StatementBuilder时,参数按索引绑定。在为实体相关操作构建 CQL 查询的所有安排中都使用StatementBuilder

  2. 当按索引绑定参数时,检查SimpleStatement会在其 CQL 中呈现参数绑定标记。CqlTemplate的 CQL 日志记录也受此更改的影响:记录的 CQL 现在包含?而不是字面值。

这些更改是允许对参数化语句进行语句准备所必需的。要运行的语句首先会被准备。然后,在第二步中,它会与其实际参数绑定,然后发送到服务器执行。

Cassandra 的 Java 驱动程序跟踪预准备语句缓存,因此在 Bean 设置方面无需执行任何操作。通常,您应该体验到更好的查询性能。另外,请记住,预准备语句缓存需要额外的内存来跟踪预准备语句。

您可以禁用CassandraTemplate及其反应式和异步变体上的预准备语句使用

var template = new CassandraTemplate(session);
template.setUsePreparedStatements(false);

您可以在Spring Data for Apache Cassandra 参考文档中找到更多详细信息。

MongoDB 的文档解包支持和宽松的聚合类型检查

值对象和记录类型帮助我们创建具有最大表达力的清晰结构化域模型。但是,持久化这些精心设计的模型并不一定会导致结构良好的数据库文档。在 Java 或 Kotlin 中看起来不错的东西可能会导致 MongoDB 本机 Document 格式中属性名称的意外重复和嵌套结构,该格式将实体嵌入到其父结构中。考虑以下简单的代码片段及其表示

class User {
  private String id;
  private Email email;
  // …
}

record Email (String email) {}

{
  "_id" : "9708-ac32-beb0",
  "email" : {
    "email" : "[email protected]"
  },
  // …
}

尽管这可以工作,但显然它不是文档存储的惯用表示,而这正是@Unwrapped发挥作用的地方。该注释允许您将属性展平(解包)到其父级中

 class User {
  private String id;
  @Unwrapped(onEmpty = OnEmpty.USE_NULL)
  private Email email;
  // …
}

@Unwrapped迫使您通过选择onEmpty(记录表示的任何字段都不存在)行为来决定如何处理不存在的值。对于那些喜欢较少冗长注释的人,可以随意使用@Unwrapped.Nullable作为替代方案,或者通过使用@Unwrapped作为元注释来创建自己的注释。无论哪种方式,生成的文档看起来都更有吸引力

{
  "_id" : "9708-ac32-beb0",
  "email" : "[email protected]",
  // …
}

存储库和MongoTemplate都能够处理解包的属性。有关更多信息,请参阅参考文档。此外,请查看@Unwrapped示例

对 jMolecules 的支持

Spring Data 存储库抽象一直是项目中的核心概念。它是一种针对在领域驱动设计 (DDD) 中创造的架构概念的编程模型:存储库抽象了一个聚合的集合。实际上,Spring Framework 本身与源自 DDD 的其他一些抽象(如服务)保持一致,并提供注释以在用户代码中表达它们。但是,用户通常不喜欢使用特定于框架的注释和抽象来表达这些概念。

jMolecules 项目专注于提供注释和基于类型的抽象,不同的技术架构概念可以与之集成。它本质上颠倒了关系:用户代码仅依赖于 jMolecules 注释和接口,然后技术集成——在第二步中——要么来自广泛的 jMolecules 集成库,要么来自框架本身。

建模关联

jMolecules 的领域驱动设计模块中的核心抽象之一是Association接口。它被类型化为AggregateRoot及其Identifier,并用于域模型以强类型方式表达与聚合的关系

class Order implements AggregateRoot<Order, OrderIdentifier> {

  OrderIdentifier id;
  Association<Customer, CustomerIdentifier> customer;
}

在这个模型中,OrderCustomer都是聚合,并且两者之间的关联通过 jMolecules Association类型显式映射。Spring Data 2021.0.0 提供了对Association的映射支持。它们被正确检测为 Spring Data 关联,并通过使用支持实例的标识符进行转换。

为了透明地启用对这些抽象的支持,请将org.jmolecules.integrations:jmolecules-spring添加到您的类路径中。Spring Data 的映射基础结构检测到这一点,并在我们对象映射工具的转换部分自动注册必要的转换器。

还为 JPA 提供了对Association实例的支持。但是,在这种情况下,Spring Data 没有提供实际的转换,而是通过与 jMolecules 本身提供的AttributeConverter实现集成来提供。使用其 ByteBuddy 扩展,您可以生成必要的AttributeConverter实现和注释配置。

标识符和聚合实例之间的映射

jMolecules 的Identifier接口鼓励对聚合使用专用的标识符类型,如前面示例中使用的OrderIdentifierCustomerIdentifier类型。当序列化Association时,我们现在实际上必须通过依次调用Association.getId()Identifier.getId()将实例转换为CustomerIdentifier,以获取要实际持久化的值。为了具体化关联,我们必须获取原始持久化值,使用名为….of(…)的公开静态工厂方法创建CustomerIdentifier实例,并最终再次调用Association.of(…)

所有这些转换步骤都在jmolecules-integrations中实现,并由 Spring Data 透明地添加到 Spring ConversionService中,供框架使用。假设OrderIdentifierUUIDString表示支持,这也意味着 Spring Data 的DomainClassConverter能够自动将完全具体化的聚合实例绑定到 Spring MVC 控制器方法

@RestController
class MyController {

  @GetMapping("/orders/{id}")
  HttpEntity<?> getOrders(@PathVariable("id") Order order) { /* … */ }
}

在此示例中,对/orders/462a692d-…的 GET 请求会自动将462a692d-…转换为OrderIdentifier,首先使用 jMolecules 转换器,然后使用为Order声明的存储库查找聚合实例。虽然通用机制在 Spring Data 中已经存在了一段时间,但 2021.0.0 版本添加了对 jMolecules Identifier实现的必要的额外集成。

Spring Data REST 的 DTO 聚合引用映射

前面提到的 jMolecules Converter实现也用于 Spring Data REST 需要获取并将聚合标识符转换为 URI 的所有位置。该模块还附带了一个新的 Jackson 反序列化器,它允许通过正确反序列化 URI 将 Spring Data REST 管理的聚合实例绑定到 DTO。假设您有由 Spring Data REST 管理并通过/orders/…公开的Order,以及如下所示的客户机控制器安排

@BasePathAwareController
class MyCustomController {

  @PostMapping("/orders")
  HttpEntity<?> postOrder(@RequestBody MyDto payload) {
    /* Process submission */
  }
}

@Data
class MyDto {
  List<Order> orders;
}

现在还假设为请求提交了以下有效负载

{
  "orders" : [
    "…/orders/462a692d-…"
  ]
}

尽管MyDto是一个普通的 data transfer object,但payload实例包含由462a692d-…标识的聚合实例作为orders链接的元素。

获取 Spring 时事通讯

与 Spring 时事通讯保持联系

订阅

领先一步

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

了解更多

获取支持

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

了解更多

即将举行的活动

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

查看全部