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 录制。

启用 Type- 和 Refactoring-Safe 的 KPropertyKPropertyPath 用于属性路径渲染

Spring Data Kotlin 集成是“语法糖”增强我们特定语言扩展的强大驱动力。Kotlin 允许将单个属性引用为属性引用(data class Book(val title: String), Book::title)。它们是 refactoring- 和 compile-safe 的,因为 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

Query by Example 是一种用户友好的查询技术,具有简单的接口。它允许动态查询创建,并且不需要编写包含字段名的查询。事实上,Query by Example 根本不需要您使用 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-r2dbc (com.oracle.database.r2dbc:oracle-r2dbc) 驱动程序。使用 Oracle ConnectionFactory 创建 DatabaseClientR2dbcEntityTemplate 会选择适当的绑定标记策略和方言。

为 Repositories 和 CassandraTemplate 启用 Cassandra 预处理语句

Spring Data for Apache Cassandra 尽可能地适应 Cassandra 的特定功能。自 2.0 版本的主要重写以来,我们为 CqlTemplate 级别的预处理语句缓存引入了 CachedPreparedStatementCreator,它允许使用纯 CQL 的预处理语句。

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

提供预处理语句功能的变化会带来一些查询方面的改变:

  1. 使用 StatementBuilder 时,参数按索引绑定。StatementBuilder 在构建与实体相关的操作的 CQL 查询时,在所有情况下都被使用。

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

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

Cassandra 的 Java 驱动程序会跟踪预处理语句缓存,因此在 bean 设置方面不需要进行任何工作。通常,您应该会体验到更好的查询性能。另外,请记住,预处理语句缓存需要额外的内存来跟踪预处理语句。

您可以禁用 CassandraTemplate 及其响应式和异步变体上的预处理语句使用。

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

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

MongoDB 的 Document Unwrapping 支持和 Relaxed Aggregation Type Checks

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

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 中供框架使用。假设 OrderIdentifierUUID 的字符串表示支持,这也意味着 Spring Data 的 DomainClassConverter 能够自动将完全具体化的聚合实例绑定到 Spring MVC 控制器方法。

@RestController
class MyController {

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

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

Spring Data REST DTOs 的聚合引用映射

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

@BasePathAwareController
class MyCustomController {

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

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

现在,假设提交的请求负载如下:

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

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

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获得支持

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

了解更多

即将举行的活动

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

查看所有