领先一步
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(…)` 的行为,我们建议您简单地创建自己的基本存储库接口(如参考文档中所述)。
第二个方法是一个简单的查询方法,它使用查询派生让 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` 和存储库抽象中都是如此。在此版本中,我们将核心值类型(例如 `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
@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
(默认为false
)来与Spring的事务同步集成。
启用此功能将导致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的发行版列车来简化数据访问层的实现。