Spring Data Dijkstra 有哪些新功能?

工程 | Oliver Drotbohm | 2014年5月21日 | ...

我们刚刚宣布了代号为 Dijkstra 的 Spring Data Release Train 的 GA 版本发布。我想借此机会带大家了解一下我们在本次发布中添加的一些功能。

5 个新模块加入 Release Train

本次发布包含的第一个重要功能是增加了 5 个模块到 Release Train 中。其中大多数模块已经存在了一段时间,但今后我们将与其它模块同步发布。新加入的模块有 Spring Data ElasticsearchCassandraCouchbaseGemfireRedis

Spring Data Commons

Release Train 的许多改进通常会包含在 Commons 模块中,以便各个存储模块都能真正受益于新添加的功能。以下是 Dijkstra 中最重要的几项:

支持包装器类型作为返回值

对于声明返回单个领域类型的 Spring Data 存储库方法,如果无法获取结果,存储库会返回 null。然而,如果我们从头开始设计 API,而不是直接返回实例,我们可能会更倾向于使用 Optional 来确保客户端不会意外地忘记进行 null 检查。

Optional 是如今不少 Java 库都提供的类型。Google Guava 有它,更重要的是,JDK 8 也提供了一个。因此,通过 Dijkstra Release Train,我们提供了使用这些类型作为返回值包装器的可能性,并让 Spring Data 存储库基础设施自动为您包装 null

interface CustomerRepository extends Repository<Customer, Long> {

  Optional<Customer> findOne(Long id);

  Optional<Customer> findByEmailAddress(EmailAddress emailAddress);
}

这里看到的第一个方法是 CrudRepository.findOne(…) 的一个变种。请注意,存储库接口不扩展 CrudRepository,因为这会导致编译错误,我们无法重新声明 findOne(…) 方法并更改返回类型。因此,我们建议您只需创建自己的基存储库接口(如参考文档中所述),以防您想更改 findOne(…) 的行为。

第二个方法是一个简单的查询方法,它使用查询派生,让 Spring Data 基础设施从方法名派生查询。我们也会在此处检测 Optional 作为包装器类型,执行查询,并自动将结果包装成一个 Optional 实例。

存储库方法的异步调用

与此类似,我们开始为存储库方法添加异步执行功能。现在,查询方法可以返回一个 Future<T>,如果它被 @Async 注解,它将被异步执行。

interface CustomerRepository extends Repository<Customer, Long> {

  @Async
  @Query(" … long running query declaration … ")
  Future<List<Customer>> findByLongRunningQuery(String lastname);
}

执行基于 Spring 对异步方法调用的支持。我们还在研究更高级的异步执行模型,例如 Promise(可能基于Project Reactor),以便在 Spring Data 的未来版本中添加。

地理空间类型

MongoDB 模块(以及其他模块)一直支持 Mongo 的地理空间操作 - 无论是 MongoTemplate 还是存储库抽象。通过本次发布,我们将核心值类型(例如 ShapePointBoxDistance 等)移至 Spring Data Commons 模块。这将允许您普遍引用地理类型,并与所有支持地理空间功能的 Spring Data 模块进行交互,而无需将这些类型相互映射。有关详细信息,请查看JavaDoc

切片 (Slices)

Spring Data 一直在存储库编程模型中支持分页。它允许您逐页访问数据,以便遍历大型数据集。除了纯分页内容外,Page 接口还公开了 API 来了解可用的元素总数和页数。

计算此数字可能非常耗费资源(因为通常需要执行额外的查询),而且通常情况下,页面信息中最有趣的元数据是它是否有下一页,这样客户端就可以继续检索更多数据。例如,您可以在 Facebook 时间线上看到这种模式。

Dijkstra 版本现在引入了一个精简版的 Page,称为 Slice,它允许您了解当前切片是否有后续切片。

interface BlogPostRepository extends Repository<BlogPost, Long> {

  Slice<BlogPost> findByAuthorOrderByDateDesc(Author author, Pageable pageable);
}

您现在可以使用 Pageable 来表达您想要获取的页码和大小,Spring Data 基础设施将读取比请求多一个项目,并使用该项目的存在或不存在作为指示,以确定下一个切片是否可用。

派生删除查询

查询方法派生功能现在支持 deleteBy…removeBy… 方法前缀,以派生基于给定条件的删除托管域类型的查询。

interface BlogPostRepository extends Repository<BlogPost, Long> {

  int deleteByDateBefore(Date date);
}

这已在 Dijkstra 的 JPA、MongoDB 和 Solr 模块中实现,并将逐步添加到其他存储模块中。

JPA 2.1 支持

Dijkstra 的 JPA 模块开发中的一个核心主题是支持 JavaEE 7 / JPA 2.1 功能。本次发布中我们解决的核心领域是对实体图在查询执行中的支持以及存储过程的执行。

实体图

假设我们有以下领域类型定义:

@Entity
@NamedEntityGraph(name = "summary", attributeNodes = { 
  @NamedAttributeNode("firstname"),
  @NamedAttributeNode("lastname")})
class Customer {
  // properties ommitted
}

现在可以通过 @EntityGraph 注解引用命名的实体图,以指示我们希望将此图应用于正在执行的查询。

interface CustomerRepository extends Repository<Customer, Long> {

  @EntityGraph("summary")
  Optional<Customer> findByEmailAddress(EmailAddress emailAddress);
}

这将导致只有 firstnamelastname 属性被急切加载,而所有其他属性都准备好在访问时延迟加载。

存储过程

JPA 2.1 增加了通过 EntityManager API 执行存储过程的功能。与上面的示例类似,存储过程的元数据可以在领域类型上声明。假设您想触发一个随机创建客户密码的存储过程:

@Entity
@NamedStoredProcedureQuery(name = "Customer.generateNewPassword", 
  procedureName = "generateNewPassword", parameters = {
    @StoredProcedureParameter(
      mode = ParameterMode.IN, name = "username", type = String.class),
    @StoredProcedureParameter(
      mode = ParameterMode.OUT, name = "password", type = String.class)})
class Customer {
  // properties ommitted
}

可以使用类似这样的存储库查询方法来执行存储过程:

interface CustomerRepository extends Repository<Customer, Long> {

  @Procedure
  String generateNewPassword(@Param("username") String username);
}

默认情况下,我们将使用众所周知的 DomainType.methodName 模式查找的存储过程元数据,并将其与方法声明匹配。对于非常简单的过程映射(如此处所示),您甚至可以省略元数据声明,因为所有元数据都可以从方法名称中推导出来。有关存储过程支持的更多信息,请参阅参考文档

Redis 的事务支持

Redis 模块的最新版本增加了累积一组可批量执行的操作的功能。为此,RedisTemplate 现在可以与 Spring 的事务同步集成,将 enableTransactionSupport 属性设置为 true(默认为 false)。

启用此功能将导致 RedisConnection 绑定到当前 Thread 并发出 MULTI 命令,这允许底层 Redis 驱动程序执行命令排队。如果事务无错误地完成,将发出 EXEC 命令;如果失败,将使用 DISCARD 命令丢弃累积的命令。

启用后,连接将绑定到当前线程,确保每个写操作都通过同一连接进行管道化并排队等待事务完成。读取操作(例如 KEYS 命令)仍将通过使用新的、非线程绑定的连接立即执行。

@Bean
public StringRedisTemplate redisTemplate() {

	StringRedisTemplate template = 
	  new StringRedisTemplate(redisConnectionFactory());
	// Enable transaction synchronization support
	template.setEnableTransactionSupport(true);

	return template;
}

这样配置的 RedisTemplate 随后可用于以下语义:

// Executed on thread bound connection
template.opsForValue().set("foo", "bar");

// Read operation executed on separate connection
template.keys("*");

// Returns null as values set within transaction are not visible
// prior to transaction flush
template.opsForValue().get("foo");

Spring Data Solr 中的复杂查询支持

使用 Spring Data Solr 的条件 API 创建更复杂的查询已被请求了很长时间。因此,我们决定重写部分实现,同时保持 API 与先前版本兼容。

基本上,我们从相对扁平的表示转移到一个类似树的模型,保留了我们一直习惯的流畅 API 风格。

现在可以如下表示 Solr 查询 q=name:solr OR (type:spring AND category:data)

new SimpleQuery(
  where("name").is("solr").or(
    where("type").is("spring").and("category").is("data")));

Spring Data REST 中的投影 (Projections)

Spring Data REST 公开的 REST 资源的常见需求是能够创建自定义表示。这意味着用户希望减少响应中渲染的属性数量,或者内联关联实体以节省服务器往返。通过 Spring Data REST 2.1,我们现在提供了在服务器端定义自定义投影的可能性。为此,您需要声明一个接口,其中包含您希望公开的属性:

@Projection(name = "summary", types = Order.class)
interface OrderSummary {
	
  LocalDate getOrderedDate();

  CustomerSummary getCustomer();

  @Value("#{@shop.calculateTotal(target)}")
  Money getTotal();
}

此接口可以放在与 Order 相同的包(或子包)中,并且会被 Spring Data REST 自动检测到。它将导致所有公开单个订单或订单集合的资源在 URI 模板中包含一个额外的参数,以指示投影功能。

{ _links : {
    orders : { href : "…/orders{?projection}", templated : true }
  }
}

如果客户端使用 projectionsummary 来展开模板,我们将在服务器端创建一个代理,并将其交给 Jackson 进行编组。每个 getter 都将被转发到实际目标类(在本例中为 Order)的属性查找。

在上面的示例中,getCustomer() 指的是一个相关实体,在非投影场景下,它将仅作为链接公开。通过使用投影,我们检测到方法的返回类型不是 Customer。这将反过来导致创建一个投影代理,以便您完全控制公开的属性。投影接口当然可以包含 Jackson 注解以进一步自定义渲染的表示。

对于高级用例,您甚至可以为投影方法配备 @Value 来将 SpEL 表达式的结果返回给 marshaller。在我们这里的示例中,我们调用一个名为 shop 的 Spring bean 的方法,并将代理目标实例传递给它来计算订单总额,该总额可以考虑折扣、税费等。

总结

通过这些精选的示例,我希望能够激发您探索 Dijkstra Release Train 中包含的模块。现在,我们将继续我们的使命,通过推出下一个名为 Evans 的 Release Train,来简化数据访问层的实现。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获得支持

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

了解更多

即将举行的活动

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

查看所有