领先一步
VMware 提供培训和认证,助您加速进步。
了解更多正如我们刚刚发布了 Spring Data Hopper 版本列车的 GA 版本一样,让我们更深入地了解该列车包含的 13 个模块所带来的变化和功能。该版本列车依赖项的一个非常基础的变化是将 Spring Framework 升级到 4.2 版本(目前为 4.2.5)作为基线。这是为即将发布的 Framework 4.3 版本做准备。我们还借此机会将 Querydsl 集成升级到 4.x 版本(目前为 4.1),这在一些核心抽象中引入了一些破坏性变更。除此之外,Hopper 在其模块中包含了相当多重大的主要版本变化。
这些升级主要是由底层存储驱动和实现的重大版本更新驱动的,这些更新需要在这些模块暴露的 API 中体现为潜在的破坏性变更。其中一些模块——例如 Spring Data Neo4j 和 Spring Data Couchbase——已经在版本列车之外发布了新的主要版本,现在又重新整合到其中。
欢迎(回归)
除了这些升级,团队还在开发许多新功能。
@AliasFor
的可组合注解$lookup
聚合和批量操作我想在本文的其余部分对其中一些功能做更详细的阐述,以便为您提供更详细的概览。
Spring Data 仓库抽象允许通过 Querydsl 执行查询方法和灵活的谓词已经有一段时间了。话虽如此,提供一个部分设置好的域类型实例作为探测器(probe)给仓库以返回与该特定探测器匹配的所有实体,这是一个长期以来备受期待的功能。Hopper 在 Spring Data Commons 中引入了对这种 Query-by-Example(示例查询)机制的通用支持,并在 JPA 和 MongoDB 模块中实现了该 API(更多功能将陆续推出)。
Query-by-Example API 由四个基本部分组成
ExampleMatcher
,其中包含有关如何匹配特定字段、null
值、通用 String
等的详细信息和策略。Example
,由探测器和 ExampleMatcher
组成。QueryByExampleExecutor
接口,您的仓库将额外实现该接口,该接口提供接受 Example
的方法,类似于 QueryDslPredicateExecutor
。在最简单的情况下,只需在域类型上设置您想要查询的值,并将该示例交给仓库即可。
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 版本列车中引入了一个称为 Projection(投影)的功能。在 Hopper 版本中,我们为 JPA 和 MongoDB 查询方法添加了在仓库级别使用 Projection 的支持。Projection 是您的域模型的自定义视图,在这种情况下,它从查询方法返回。
@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 类。使用 Projection,您只需定义一个接口,其中包含您想要暴露的属性(getter 方法),并将该 Projection 接口用作查询方法的返回类型。
interface NoAddresses {
String getFirstName();
String getLastName();
}
interface PersonRepository extends CrudRepository<Person, Long> {
NoAddresses findByFirstName(String firstName);
}
Projection 是一种强大的模式,可以从现有模型构建调整后的视图。NoAddresses
是一个封闭式 Projection,因为它不包含任何动态计算值的方法(更多内容请参阅下文)。封闭式 Projection 使我们能够优化查询执行,因为只从数据存储中查询暴露的属性。因此,在上述情况下,实际执行的查询在语义上等同于 select u.firstName, u.lastName from User u where u.firstName = ?1
。返回的元组然后被包装到一个代理中,该代理返回与声明的访问器对应的值。
然而,Projection 也可以用于丰富数据模型。您可以使用 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 上的方法并将目标传递给它以用于高级计算。在这种情况下,不会应用查询优化,因为为该接口创建的代理将需要访问原始目标实例。
有关如何在查询方法中使用 Projection 的更多详细信息,请参阅 Spring Data JPA 和 Spring Data MongoDB 的参考文档。
对 Redis 集群的支持在现有的具有集群功能的 Redis 驱动程序之上提供了一个高级 API。集群支持基于与非集群通信完全相同的构建块。RedisClusterConnection
是 RedisConnection
的扩展,负责处理与 Redis 集群的通信,并将错误转换为 Spring 的 DataAccessException
层次结构。您会发现,与您在使用 Spring Data Redis 时已经习惯的方式没有太大区别。
Redis 集群的行为与单节点 Redis 甚至 Sentinel 监控的主从环境不同。这是由于自动分片将一个键映射到 16384 个槽中的一个,这些槽分布在各个节点上。因此,涉及多个键的命令必须断言所有键都映射到完全相同的槽,以避免跨槽执行错误。
RedisClusterConnection
既提供了与单个槽或节点通信的 API,又在与集群交互时保持了预期的行为。它负责执行涉及多个键、槽或集群节点的命令,因此会连接到所需的节点并收集结果,以便例如 KEYS
命令不仅返回单个节点的匹配键,还会返回集群内所有匹配键的累积列表。
更多信息以及如何设置 Spring Data Redis 以与集群一起工作的完整示例可以在 Spring Data Examples GitHub 仓库的 Redis Cluster 模块以及参考文档中找到。
借助 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 版本列车添加了声明所谓的 lookup 类型的方法,对于这些类型,Spring Data REST 会在表示中内联渲染一个独立属性,并注册相应的 Jackson 反序列化器,以确保该属性值在处理 PUT
和 POST
请求时被转换回该值类型的实例。
假设包含 Country
实例的资源的原始表示如下:
{
"zipCode" : "…",
"_links" {
"country" : { "href" : "…" }
}
}
如果您现在将 Country
注册为 lookup 类型,如下所示:
@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 相关内容的最新和最重要进展。