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 repository 通常在应用启动时引导,因此它们自然会影响启动时间。Pascal 版本引入了与 Spring Framework 对捕获启动事件的支持的集成,该支持自版本 5.3 起可用。通过启用 JFR 记录,您可以收集和分析以下 repository 启动事件

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

  • spring.data.repository.scanning: Repository 接口扫描

对于每个 Repository

  • spring.data.repository.init: Repository 初始化

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

  • spring.data.repository.composition: Repository 组合的组装

  • spring.data.repository.target: Repository 目标创建

  • spring.data.repository.proxy: Repository 代理创建

  • spring.data.repository.postprocessors: Repository 代理后处理

您可以通过在所有 Java 9 或更新的运行时或 Java 8 update 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

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 会选择适当的绑定标记策略和方言。

为 Repository 和 CassandraTemplate 启用 Cassandra Prepared Statements

Spring Data for Apache Cassandra 在可能的情况下会适应 Cassandra 特定的功能。自其 2.0 版本进行重大重写以来,我们在 CqlTemplate 级别引入了 CachedPreparedStatementCreator 用于预处理语句缓存,这允许使用纯 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]",
  // …
}

Repository 和 MongoTemplate 都可以处理解包的属性。有关更多信息,请参阅参考文档。此外,还可以查看@Unwrapped 的示例

支持 jMolecules

Spring Data repository 抽象一直是项目中的核心概念。它是领域驱动设计(DDD)中提出的一个架构概念的编程模型:repository 抽象了聚合的集合。实际上,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 添加到您的 classpath。Spring Data 的映射基础设施会检测到这一点,并自动在我们的对象映射工具的转换部分注册必要的转换器。

Association 实例的支持也提供给 JPA。但是,在这种情况下,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 请求会首先使用 jMolecules 转换器自动将 462a692d-… 转换为 OrderIdentifier,然后使用为 Order 声明的 repository 来查找聚合实例。通用机制在 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 管理并通过 /orders/… 暴露的 Order,以及一个客户控制器安排如下:

@BasePathAwareController
class MyCustomController {

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

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

现在也假设为该请求提交了以下 payload

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

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

获取 Spring 新闻简报

订阅 Spring 新闻简报,保持联系

订阅

超越

VMware 提供培训和认证,助您快速提升。

了解更多

获取支持

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

了解更多

即将举办的活动

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

查看全部