Spring Data 2021.0增加了对DDD聚合更多自动支持!


Spring Data 2021.0(代号为Pascal)是继六个月的新节奏之后的第二个版本。它附带了对许多现有接口和编程模型的改进。这篇博客文章解释了以下主题:
 
1. 为CrudRepository和ReactiveCrudRepository引入了新的删除方法deleteAllById
在Spring Data 2.0发行版中,我们重命名了CrudRepository方法以表示特定方法将接受的参数。重命名后,删除方法看起来像deleteById(ID id)和deleteAll(Iterable<? extends T> entities)。根据数据存储的实际情况,这可以是批量删除(通过查询删除),具体取决于数据存储模块。
 
2.支持Spring Core Java Flight Recorder(JFR)指标
Java Flight Recorder(JFR)是一种工具,用于收集,诊断和配置有关正在运行的Java应用程序的数据。它与Java运行时的紧密集成允许在生产环境中以低开销收集事件。
Spring Data存储库通常在应用程序启动时进行引导,因此它们自然会增加启动时间。Pascal发行版引入了与Spring Framework支持的集成,以支持捕获启动事件,此功能自5.3版本起可用
 
3.删除Apache Solr
小组已决定停止维护Solr的模块。展望未来,我们建议使用Spring Data Elasticsearch作为全文搜索安排的首选Spring Data模块。Spring Data Elasticsearch是一个积极维护的社区模块。
 
4.使用QueryByExample支持R2DBC 和Oracle
通过示例查询是一种具有简单界面的用户友好查询技术。它允许动态查询创建,并且不需要编写包含字段名称的查询。实际上,“示例查询”根本不需要您使用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();

 
5.启用 Cassandra Prepared 
Apache Cassandra的Spring Data尽可能容纳Cassandra特定的功能。自从它在2.0版中进行重大重写,引入了预备语句缓存,从而允许使用纯CQL使用预备语句。
 
6. 支持jMolecules
Spring Data存储库抽象一直是项目中的核心概念。它是针对领域驱动设计(DDD)中提出的体系结构概念的编程模型:存储库抽象了集合的集合。实际上,Spring框架本身与源自DDD的其他一些抽象(例如服务)保持一致,并提供注释以在用户代码中表达它们。但是,用户通常不喜欢使用特定于框架的注释和抽象来表达那些概念。
jMolecules项目仅专注于提供注释和基于类型的抽象与对技术不同的架构概念可以整合。它实质上颠倒了这种关系:用户代码仅取决于jMolecules注释和接口,然后在第二步中从广泛的jMolecules集成库或框架本身提供技术集成。
Association接口是jMolecules的领域驱动设计模块中的核心抽象之一,它的类型一AggregateRoot以及其Identifier是领域模型用来表达对一个强类型的方式聚集的关系:
class Order implements AggregateRoot<Order, OrderIdentifier> {

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

Spring Data 2021.0.0随附了对的Association映射支持。它们被正确地检测为Spring数据关联,并通过使用支持实例的标识符进行转换。
要透明地启用对这些抽象的支持,请添加org.jmolecules.integrations:jmolecules-spring到您的类路径中。Spring Data的映射基础结构会检测到该情况,并在我们的对象映射工具的转换部分中自动注册必要的转换器。
Association还为JPA提供了实例支持。但是,在这种情况下,Spring Data不提供实际的翻译,而是通过AttributeConverter集成jMolecules本身提供的实现。使用其ByteBuddy扩展,您可以生成必要的AttributeConverter实现和注释配置。
 
标识符和聚合实例之间的映射
jMolecules的Identifier接口鼓励在聚合中使用专用标识符类型,如先前示例中使用的OrderIdentifier和CustomerIdentifier类型。当序列化Association,我们现在实际上可有效地通过调用Association.getId()和Identifier.getId()将实例变为CustomerIdentifier,其中值会持久。
为了物化持久存储关联,我们必须获取原始的持久值,,可以使用名为….of(…)的静态公开工厂方法创建创建一个CustomerIdentifier实例,并最终再次调用Association.of(…)。
所有这些转换步骤都是在jmolecules-integrationsSpring Data中实现的,并由Spring Data透明地添加到Spring中ConversionService供框架使用。
假设OrderIdentifier在数据库是以UUID表示String类型,这也意味着Spring DataDomainClassConverter能够将完全实现的聚合实例自动绑定到Spring MVC控制器方法:

@RestController
class MyController {

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

这里GET请求/orders/462a692d-…会自动转换462a692d-…到OrderIdentifier变量中,这是使用jMolecules转换器,然后使用寻找聚合名Order的实例。
虽然这种生成机制在Spring Data中已经使用了一段时间,但2021.0.0版本为jMolecules的Identifier 实现添加了必要的附加集成。
 
Spring Data REST聚合到DTO的映射
前面提到的jMoleculesConverter实现还用于所有需要Spring Data REST获取并将聚合标识符转换为URI的地方。该模块还附带了一个新的Jackson序列化器,该序列化器允许通过适当地反序列化URI将Spring Data REST管理的聚合实例绑定到DTO中。
假设您Order已由Spring Data REST管理并通过如下所示/orders/…的客户控制器公开:
@BasePathAwareController
class MyCustomController {

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

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

假设请求对API提交了以下JSON内容:
{
  "orders" : [
   
"…/orders/462a692d-…"
  ]
}

尽管MyDto是普通的数据传输对象,但其实例中orders会包含由标识462a692d-…为元素的聚合实例。