抢先一步
VMware 提供培训和认证,助您加速发展。
了解更多我们刚刚宣布了名为 Dijkstra 的 Spring Data 发行火车 GA 版本的可用性。我想借此机会向您介绍在此版本中添加的一些功能。
该版本包含的第一个重要特性是向发行火车添加了 5 个模块。其中大多数已经存在了一段时间,但从今往后,我们将与其它模块同步发布它们。新添加的模块包括 Spring Data Elasticsearch、Cassandra、Couchbase、Gemfire 和 Redis。
发行火车的许多改进通常会落入 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 了解详情。
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 模块中实现,未来将添加到其他存储模块中。
在 Dijkstra 的 JPA 模块开发期间,一个核心主题是对 JavaEE 7 / JPA 2.1 功能的支持。我们在本次发布中解决的核心领域是对查询执行中的实体图(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);
}
这将导致只有 firstname
和 lastname
属性被急切加载,而其他所有属性都准备在访问时延迟加载。
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 模块的最新版本添加了累积一组可批量执行操作的功能。为了实现这一点,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 的 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 暴露的 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 的发行火车,继续我们的使命,以简化数据访问层的实现。