Spring Data Dijkstra 有哪些新特性?

工程 | Oliver Drotbohm | May 21, 2014 | ...

我们刚刚宣布了名为 Dijkstra 的 Spring Data 发行火车 GA 版本的可用性。我想借此机会向您介绍在此版本中添加的一些功能。

5 个新模块加入发行火车

该版本包含的第一个重要特性是向发行火车添加了 5 个模块。其中大多数已经存在了一段时间,但从今往后,我们将与其它模块同步发布它们。新添加的模块包括 Spring Data ElasticsearchCassandraCouchbaseGemfireRedis

Spring Data Commons

发行火车的许多改进通常会落入 Commons 模块,以便各个存储模块都可以从新添加的功能中受益。以下是 Dijkstra 中最重要的几项改进:

支持包装类型作为返回值

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

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

interface CustomerRepository extends Repository<Customer, Long> {

  Optional<Customer> findOne(Long id);

  Optional<Customer> findByEmailAddress(EmailAddress emailAddress);
}

您在此处看到的第一个方法是 CrudRepository.findOne(…) 的一个变体。请注意,存储库接口不继承 CrudRepository,因为这会导致编译错误,因为我们无法重新声明 findOne(…) 方法并更改返回类型。因此,如果您想改变 findOne(…) 的行为,我们建议您简单地创建自己的基础存储库接口(如参考文档中所述)。

第二个方法是一个简单的查询方法,它使用查询派生(query derivation)让 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 对异步方法调用的支持。我们还在研究更高级的异步执行模型,例如承诺(可能基于Project Reactor),以便在未来的 Spring Data 版本中添加。

地理空间类型

MongoDB 模块(以及其他模块)一直支持 Mongo 的地理空间操作——无论是在 MongoTemplate 中还是在存储库抽象中。在当前版本中,我们将核心值类型(例如 Shape, Point, Box, Distance 等)移到了 Spring Data Commons 模块中。这将允许您普遍地引用地理类型并与所有支持地理空间功能的 Spring Data 模块交互,而无需将这些类型相互映射。查看 JavaDoc 了解详情。

Slice(切片)

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 graphs)支持以及存储过程的执行。

实体图(Entity graphs)

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

@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 现在可以通过将 enableTransactionSupport 属性配置为 true 来与 Spring 的事务同步集成(默认为 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 的 criteria 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 中的投影

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 }
  }
}

如果客户端现在使用 summary 扩展模板中的 projection 参数,我们将在服务器端创建一个代理,该代理将交给 Jackson 进行编组。每个 getter 都将被转发到实际目标类(在此示例中为 Order)上的属性查找。

在上面的示例中,getCustomer() 指的是一个相关实体,在非投影场景下它只会被暴露为一个链接。通过使用投影,我们检测到方法的返回类型不是 Customer。这反过来会导致创建一个投影代理,以便您可以完全控制暴露的属性。投影接口当然可以携带 Jackson 注解来进一步定制呈现的表示。

对于高级用例,您甚至可以使用 @Value 注解修饰投影方法,以便将 SpEL 表达式的结果返回给编组器。在我们这里的示例中,我们调用一个名为 shop 的 Spring Bean 上的方法,并将代理目标实例传递给它来计算订单总额,这可能考虑了折扣、税费等。

总结

希望通过这些精选示例,我能够激发您探索 Dijkstra 发行火车中所包含模块的好奇心。我们现在将通过启动下一个名为 Evans 的发行火车,继续我们的使命,以简化数据访问层的实现。

获取 Spring 新闻简报

订阅 Spring 新闻简报保持联系

订阅

抢先一步

VMware 提供培训和认证,助您加速发展。

了解更多

获取支持

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

了解更多

即将到来的活动

查看 Spring 社区中所有即将到来的活动。

查看全部