领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多正如我们刚刚发布了 Spring Data 发布列车 Hopper 的 GA 版本,让我们深入了解一下该列车 13 个模块带来的变化和特性。发布列车依赖项中一项非常基本的变化是升级到 Spring Framework 4.2(当前为 4.2.5)作为基线。这是为即将发布的框架 4.3 版本做准备。我们还借此机会将 Querydsl 集成升级到 4.x(当前为 4.1),这需要在一些非常核心的抽象中进行一些重大更改。除此之外,Hopper 还包含了其模块中相当多的重大版本变更。
这些升级主要是由底层存储驱动程序和实现的主要版本升级驱动的,这些升级需要反映在这些模块公开的 API 中的潜在重大更改中。其中一些模块(如 Spring Data Neo4j 和 Spring Data Couchbase)已经在发布列车之外发布了新的主要版本,现在已重新集成到其中。
欢迎(回来)
除了这些升级之外,团队还在开发一系列新功能。
@AliasFor
的组合注解$lookup
聚合和批量操作我想在博客文章的剩余部分进一步阐述其中一些功能,以便为您提供更详细的概述。
Spring Data 存储库抽象允许执行查询方法并通过 Querydsl 使用灵活的谓词已经有一段时间了。也就是说,长期以来一直有一个请求的功能,即能够提供一个部分设置的域类型实例作为探针到存储库,以返回与该特定探针匹配的所有实体。Hopper 在 Spring Data Commons 中引入了对这种示例查询机制的通用支持,并在 JPA 和 MongoDB 模块中实现了 API(更多内容即将推出)。
示例查询 API 由四个基本部分组成
ExampleMatcher
,它包含有关如何匹配特定字段、null
值、String
等的详细信息和策略。Example
,它由探针和 ExampleMatcher
组成。QueryByExampleExecutor
接口,您的存储库将另外实现该接口,并且该接口提供类似于 QueryDslPredicateExecutor
的采用 Example
的方法。在最简单的情况下,只需在域类型上设置要查询的值,并将该示例传递给存储库即可。
interface PersonRepository extends CrudRepository<Person, Long>,
QueryByExampleExecutor<Person> { … }
Example<Person> example = Example.of(new Person("Jon", "Snow"));
Iterable<Person> result = repository.findAll(example);
默认情况下,给定值按原样匹配,在查询创建期间忽略 null
值。您可以通过提供 ExampleMatcher
来全面或针对各个字段自定义处理,从而获得对匹配过程的更多控制。
ExampleMatcher exampleSpec = new ExampleMatcher()
.withMatcher("firstname", endsWith())
.withMatcher("lastname", startsWith().ignoreCase());
上面的规范例如在上面的示例中为 JPA 创建谓词 like(firstname, "%Jon")
和 like(lower(expression), "snow%")
。
还有更多选项可用。因此,请查看 Spring Data JPA 和 MongoDB 的参考文档。
Spring Data REST 发布的投影概念在 Evans 发布 列车中引入了一个名为投影的功能。在 Hopper 中,我们为 JPA 和 MongoDB 查询方法添加了支持,以便在存储库级别使用投影。投影是您域模型的自定义视图,在本例中,它是从查询方法返回的。
@Entity
public class Person {
@Id @GeneratedValue
private Long id;
private String firstName, lastName;
@OneToOne
private Address address;
}
@Entity
public class Address {
@Id @GeneratedValue
private Long id;
private String street, state, country;
}
将 Person
的数据暴露限制为 firstName
和 lastName
需要一个专门的 DTO 类。使用投影,您只需定义一个包含要公开的属性(getter 方法)的接口,并将投影接口用作查询方法的返回类型
interface NoAddresses {
String getFirstName();
String getLastName();
}
interface PersonRepository extends CrudRepository<Person, Long> {
NoAddresses findByFirstName(String firstName);
}
投影是一种强大的模式,可以从现有模型构建调整后的视图。NoAddresses
是一个封闭的投影,因为它不包含任何动态计算值的任何方法(请参阅下面关于此的更多信息)。封闭投影允许我们优化查询执行,因为仅从数据存储中查询公开的属性。因此,在上述情况下,实际执行的查询在语义上等效于 select u.firstName, u.lastName from User u where u.firstName = ?1
。然后将返回的元组包装到一个代理中,该代理返回对应于声明的访问器的值。
但是,投影也可以用于丰富数据模型。您可以使用 SpEL 表达式用 @Value
注释公开的属性以公开合成属性。
interface FullNameAndCountry {
String getFirstName();
String getLastName();
@Value("#{target.firstName} #{target.lastName}")
String getFullName();
@Value("#{target.address.country}")
String getCountry();
@Value("#{@mybean.someMethod(target)}")
String getSomeCalculatedValue()
}
请注意,我们如何利用目标实例的属性、遍历甚至不在顶级公开的嵌套属性,甚至在其他 Spring bean 上调用方法并将目标传递给它以用于高级计算。在这种情况下,不会应用任何查询优化,因为为此接口创建的代理将需要访问原始目标实例。
请参阅 Spring Data JPA 和 Spring Data MongoDB 参考文档,以深入了解如何将投影与查询方法一起使用。
对 Redis 集群的支持在现有的具有集群功能的 Redis 驱动程序之上提供了一个高级 API。集群支持基于与非集群通信完全相同的构建块。RedisClusterConnection
(RedisConnection
的扩展)处理与 Redis 集群的通信,并将错误转换为 Spring 的 DataAccessException
层次结构。您会发现,在使用 Spring Data Redis 时,与您已习惯的操作没有太大区别。
Redis 集群的行为与单个节点 Redis 甚至哨兵监视的主从环境不同。这是由自动分片引起的,该分片将密钥映射到 16384 个插槽之一,这些插槽分布在节点之间。因此,涉及多个密钥的命令必须断言所有密钥都映射到完全相同的插槽,以避免跨插槽执行错误。
RedisClusterConnection
提供了与单个插槽或节点通信的 API,但在与集群交互时保留了预期的行为。它负责执行涉及多个密钥、插槽或集群节点的命令,因此连接到所需的节点并收集结果,以便例如 KEYS
命令不仅返回单个节点的匹配密钥,还返回集群中所有匹配密钥的累积列表。
有关如何设置 Spring Data Redis 以与集群一起工作的更多信息和完整示例,请参阅 Spring Data Examples GitHub 存储库的 Redis 集群模块 以及 参考文档。
使用 Redis 存储库,Hopper 提供了在 Redis 之上构建的 Spring Data 存储库抽象的实现,以便您可以执行基本的 CRUD 操作和执行派生查询方法。它允许您将域对象无缝地转换为 Redis 哈希并存储,应用自定义映射策略并利用二级索引。让我们来看一个示例域类型、一个存储库以及启动 Redis 存储库所需的必要 Spring 配置。
@RedisHash("persons")
class Person {
@Id String id;
String firstname;
@Indexed String lastname;
Address address;
}
interface PersonRepository extends CrudRepository<Person, String> {
List<Person> findByLastname(String lastname);
}
@Configuration
@EnableRedisRepositories
class ApplicationConfig {
@Bean
RedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory();
}
@Bean
RedisTemplate<?, ?> redisTemplate() {
return new RedisTemplate<byte[], byte[]>(connectionFactory());
}
}
请注意,在 firstname
上使用的 @Indexed
注解允许在派生查询方法中使用该属性。这也适用于嵌套或嵌入对象。有关自定义对象映射策略、过期时间和侦听器以及存储对象引用的更多信息,请参阅Spring Data Redis 参考文档。
Spring Data REST 的一个常见请求是使用聚合体的唯一属性的值来构成公开的项目资源的 URI。想象一个非常简单的实体 Country
,您希望在其 URI 中使用其唯一名称。
@Entity
class Country {
@Id @GeneratedValue
private Long id;
private String name;
}
为了实现这一点,Spring Data REST 的 RepositoryRestConfiguration
现在允许通过专用 API 自定义实体查找。使用不同的属性作为 URI 实际上需要定义两件事:要使用的属性以及存储库上的查询方法,以将该属性的值映射回其实例。如果您使用的是 Java 8,则注册看起来像这样
@Component
public class SpringDataRestCustomization extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config) {
config.withCustomEntityLookup().
forRepository(CountryRepository.class,
Country::getName, CountryRepository::findByName);
}
}
如您所见,我们在这里使用方法句柄来定义来回映射步骤,以便基础结构能够获取这些步骤。当然,此处显示的方法还有另一种重载,也可以在 Java 6 上使用。有关此内容的详细信息,请确保查看GitHub 上专门介绍此内容的示例。
很多时候,域模型包含作为值对象但实际上表示一组专用可能值中的特定值的类型。上面示例中的 Country
类实际上属于此类。因为我们需要管理值的超集,所以有一个存储库到位。如果也应该允许通过 REST 管理该集合,则也需要导出该存储库。由于存储库通常表示要管理的聚合,因此 Spring Data REST 处理这种情况的默认方式是在遇到 Country
实例的任何位置呈现到关联资源的链接。Hopper 发布列车添加了声明所谓查找类型的方法,对于这些类型,Spring Data REST 然后呈现内联在表示中的单个属性,并注册相应的 Jackson 反序列化器,以确保该属性值在 PUT
和 POST
请求中被转换回该值类型的实例。
假设包含 Country
实例的资源的原始表示
{
"zipCode" : "…",
"_links" {
"country" : { "href" : "…" }
}
}
如果您现在继续将 Country
注册为查找类型,如下所示
@Component
public class SpringDataRestCustomization extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config) {
config.withCustomEntityLookup().
forLookupRepository(CountryRepository.class).
withIdMapping(Country::getName).
withLookup(CountryRepository::findByName);
}
}
表示将更改为以下内容,但仍将在模型中维护实体语义
{
"zipCode" : "…",
"country" : "Germany"
}
升级到 Spring 4.2 作为框架基线,使我们能够提供组合您自己的注解的增强选项。我们将基本基础结构添加到 Spring Data Commons 模块,并调整了 JPA、MongoDB 和 Redis 的实现,以允许您利用这些更改。
假设您在很多地方将 Spring Data JPA 注解 @Modifying
和 @Query
一起使用,如下所示
@Modifying
@Query("update #{#entityName} u set u.active = ?1 where u.id in ?2")
void updateUserActiveState(boolean activeState, Integer... ids);
@Modifying
@Query
@Retention(RetentionPolicy.RUNTIME)
public @interface ModifyingQuery {
@AliasFor(annotation = Query.class, attribute = "value")
public String query();
}
@ModifyingQuery(query =
"update #{#entityName} u set u.active = ?1 where u.id in ?2")
void updateUserActiveState(boolean activeState, Integer... ids);
或者只是玩得开心一点,将现有注解翻译成您喜欢的语言。
Spring Data 团队即将前往拉斯维加斯!请务必加入我们,参加今年 SpringOne Platform 上关于 Spring Data 的众多研讨会。第一批特色演讲已在活动网站上发布。请务必注册以了解 Spring Data 的最新信息,以及 Spring Framework 生态系统以及与 CloudFoundry 相关的所有内容。