原标题:Spring认证|Spring Data JPA 参考文档二(内容来源:Spring中国教育管理中心)
查询方法,返回多个结果可以使用标准的Java Iterable,List和Set。除此之外,我们支持返回 Spring Data 的Streamable、 的自定义扩展Iterable以及Vavr提供的集合类型。请参阅解释所有可能的查询方法返回类型的附录。
您可以使用任何集合类型的Streamable替代Iterable品。它提供了访问非并行Stream(缺少 from Iterable)的便捷方法,以及直接….filter(…)和….map(…)覆盖元素并将其连接Streamable到其他元素的能力:
示例 19. 使用 Streamable 组合查询方法结果
interface PersonRepository extends Repository {
Streamable findByFirstnameContaining(String firstname);
Streamable findByLastnameContaining(String lastname);
}
Streamable result = repository.findByFirstnameContaining("av")
.and(repository.findByLastnameContaining("ea"));
为集合提供专用包装器类型是一种常用模式,用于为返回多个元素的查询结果提供 API。通常,通过调用存储库方法返回类集合类型并手动创建包装器类型的实例来使用这些类型。您可以避免额外的步骤,因为 Spring Data 允许您使用这些包装器类型作为查询方法返回类型,如果它们满足以下条件:
以下清单显示了一个示例:
class Product {
MonetaryAmount getPrice() { … }
}
@RequiredArgsConstructor(staticName = "of")
class Products implements Streamable {
private final Streamable streamable;
public MonetaryAmount getTotal() {
return streamable.stream()
.map(Priced::getPrice)
.reduce(Money.of(0), MonetaryAmount::add);
}
@Override
public Iterator iterator() {
return streamable.iterator();
}
}
interface ProductRepository implements Repository {
Products findAllByDescriptionContaining(String text);
}
一个Product暴露的API来访问产品的价格实体。
Streamable
包装器类型公开了一个额外的API,在Streamable
实现Streamable接口并委托给实际结果。
该包装器类型Products可以直接用作查询方法返回类型。您不需要Streamable
Vavr是一个包含 Java 函数式编程概念的库。它附带一组自定义集合类型,您可以将其用作查询方法返回类型,如下表所示:
您可以使用第一列(或其子类型)中的类型作为查询方法返回类型,并根据实际查询结果(第三列)的 Java 类型获取第二列中的类型作为实现类型。或者,您可以声明Traversable(Iterable相当于Vavr ),然后我们从实际返回值派生实现类。也就是说, ajava.util.List变成了 VavrList或Seq, ajava.util.Set变成了 Vavr LinkedHashSet Set,依此类推。
从 Spring Data 2.0 开始,返回单个聚合实例的存储库 CRUD 方法使用 Java 8Optional来指示可能缺少值。除此之外,Spring Data 支持在查询方法上返回以下包装器类型:
或者,查询方法可以选择根本不使用包装器类型。然后通过返回来指示不存在查询结果null。返回集合、集合替代、包装器和流的存储库方法保证永远不会返回null,而是返回相应的空表示。有关详细信息,请参阅“存储库查询返回类型”。
您可以使用Spring Framework 的可空性注释来表达存储库方法的可空性约束。它们null在运行时提供了一种工具友好的方法和选择加入检查,如下所示:
Spring 注释使用JSR 305注释(一种休眠但广泛使用的 JSR)进行元注释。JSR 305 元注释让工具供应商(例如IDEA、Eclipse和Kotlin)以通用方式提供空安全支持,而无需对 Spring 注释进行硬编码支持。要为查询方法启用可空性约束的运行时检查,您需要使用 Spring 的@NonNullApiin在包级别激活非可空性package-info.java,如以下示例所示:
示例 20. 在 package-info.java
@org.springframework.lang.NonNullApi
package com.acme;
一旦非空默认设置到位,存储库查询方法调用将在运行时验证为可空性约束。如果查询结果违反了定义的约束,则抛出异常。当该方法将返回null但被声明为不可为空时(在存储库所在的包上定义的默认注释),就会发生这种情况。如果您想再次选择可空结果,请有选择地使用@Nullable单个方法。使用本节开头提到的结果包装器类型继续按预期工作:空结果被转换为表示不存在的值。
以下示例显示了刚刚描述的许多技术:
示例 21. 使用不同的可空性约束
package com.acme;
import org.springframework.lang.Nullable;
interface UserRepository extends Repository {
User getByEmailAddress(EmailAddress emailAddress);
@Nullable
User findByEmailAddress(@Nullable EmailAddress emailAdress);
Optional findOptionalByEmailAddress(EmailAddress emailAddress);
}
存储库驻留在我们定义了非空行为的包(或子包)中。
EmptyResultDataAccessException当查询未产生结果时抛出。IllegalArgumentException当emailAddress传递给方法是时抛出null。
null当查询未产生结果时返回。也接受null作为的值emailAddress。
Optional.empty()当查询未产生结果时返回。IllegalArgumentException当emailAddress传递给方法是时抛出null。
Kotlin在语言中定义了可空性约束。Kotlin 代码编译为字节码,它不通过方法签名而是通过编译元数据来表达可空性约束。确保kotlin-reflect在您的项目中包含JAR 以启用对 Kotlin 的可空性约束的内省。Spring Data 存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:
示例 22.在 Kotlin 存储库上使用可空性约束
interface UserRepository : Repository {
fun findByUsername(username: String): User
fun findByFirstname(firstname: String?): User?
}
该方法将参数和结果都定义为不可为空(Kotlin 默认值)。Kotlin 编译器拒绝传递null给方法的方法调用。如果查询产生空结果,
EmptyResultDataAccessException则抛出an 。
该方法接受null的firstname参数,并返回null,如果查询不产生结果。
您可以使用 Java 8Stream
示例 23. 使用 Java 8 流式传输查询结果 Stream
@Query("select u from User u")
Stream findAllByCustomQueryAndStream();
Stream readAllByFirstnameNotNull();
@Query("select u from User u")
Stream streamAllPaged(Pageable pageable);
AStream潜在地包装了底层数据存储特定的资源,因此必须在使用后关闭。您可以Stream使用close()方法或使用Java 7try-with-resources块手动关闭,如以下示例所示:
示例 24.Stream
try (Stream stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
并非所有Spring Data模块当前都支持Stream
您可以使用Spring 的异步方法运行能力异步运行存储库查询。这意味着该方法在调用时立即返回,而实际查询发生在已提交给 Spring 的任务中TaskExecutor。异步查询不同于反应式查询,不应混合使用。有关反应式支持的更多详细信息,请参阅商店特定的文档。以下示例显示了一些异步查询:
@Async
Future findByFirstname(String firstname);
@Async
CompletableFuture findOneByFirstname(String firstname);
@Async
ListenableFuture findOneByLastname(String lastname);
使用
java.util.concurrent.Future作为返回类型。
使用Java
8java.util.concurrent.CompletableFuture作为返回类型。
使用
aorg.springframework.util.concurrent.ListenableFuture作为返回类型。
本节介绍如何为定义的存储库接口创建实例和 bean 定义。一种方法是使用支持存储库机制的每个 Spring Data 模块附带的 Spring 命名空间,尽管我们通常建议使用 Java 配置。
每个 Spring Data 模块都包含一个repositories元素,可让您定义 Spring 为您扫描的基本包,如以下示例所示:
示例 25. 通过 XML 启用 Spring Data 存储库
在前面的示例中,指示 Spring 扫描com.acme.repositories及其所有子包以查找扩展Repository的接口或其子接口之一。对于找到的每个接口,基础结构注册特定FactoryBean于持久性技术以创建处理查询方法调用的适当代理。每个 bean 都在从接口名称派生的 bean 名称下注册,因此 的接口UserRepository将在 下注册userRepository。嵌套存储库接口的 Bean 名称以其封闭的类型名称为前缀。该base-package属性允许使用通配符,以便您可以定义扫描包的模式。
默认情况下,基础设施会选择每个接口,这些接口扩展Repository位于配置的基本包下的持久性技术特定的子接口,并为其创建一个 bean 实例。但是,您可能希望更精细地控制哪些接口为其创建了 bean 实例。为此,请在元素内使用
例如,要将某些接口从实例化中排除为存储库 bean,您可以使用以下配置:
示例 26. 使用 exclude-filter 元素
前面的示例排除了所有以SomeRepository实例化结尾的接口。
您还可以通过@Enable${store}Repositories在 Java 配置类上使用特定于商店的注释来触发存储库基础结构。有关 Spring 容器的基于 Java 的配置的介绍,请参阅Spring 参考文档中的 JavaConfig。
启用 Spring Data 存储库的示例配置类似于以下内容:
示例 27. 基于注解的存储库配置示例
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
前面的示例使用特定于 JPA 的注释,您可以根据实际使用的商店模块更改该注释。这同样适用于EntityManagerFactorybean的定义。请参阅涵盖商店特定配置的部分。
您还可以在 Spring 容器之外使用存储库基础设施——例如,在 CDI 环境中。您的类路径中仍然需要一些 Spring 库,但通常,您也可以通过编程方式设置存储库。提供存储库支持的 Spring Data 模块附带了RepositoryFactory您可以使用的特定于持久性技术的技术,如下所示:
示例 28. 存储库工厂的独立使用
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
Spring Data 提供了各种选项来创建只需很少编码的查询方法。但是当这些选项不符合您的需求时,您还可以为存储库方法提供您自己的自定义实现。本节介绍如何做到这一点。
要使用自定义功能丰富存储库,您必须首先定义片段接口和自定义功能的实现,如下所示:
示例 29. 自定义存储库功能的接口
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
示例 30. 自定义存储库功能的实现
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
与片段接口对应的类名中最重要的部分是Impl后缀。
实现本身不依赖于Spring Data,可以是一个普通的Spring bean。因此,你可以使用标准的依赖注入行为来注入对其他bean(例如a JdbcTemplate)的引用,参与方面等等。
然后你可以让你的repository接口扩展fragment接口,如下:
示例 31. 对存储库界面的更改
interface UserRepository extends CrudRepository, CustomizedUserRepository {
// Declare query methods here
}
使用您的存储库接口扩展片段接口结合了 CRUD 和自定义功能,并使其可供客户端使用。
Spring Data 存储库是通过使用形成存储库组合的片段来实现的。片段是基础存储库、功能方面(例如QueryDsl)和自定义接口及其实现。每次向存储库界面添加界面时,您都可以通过添加片段来增强组合。每个 Spring Data 模块都提供基本存储库和存储库方面的实现。
以下示例显示了自定义接口及其实现:
示例 32. 片段及其实现
interface HumanRepository {
void someHumanMethod(User user);
}
class HumanRepositoryImpl implements HumanRepository {
public void someHumanMethod(User user) {
// Your custom implementation
}
}
interface ContactRepository {
void someContactMethod(User user);
User anotherContactMethod(User user);
}
class ContactRepositoryImpl implements ContactRepository {
public void someContactMethod(User user) {
// Your custom implementation
}
public User anotherContactMethod(User user) {
// Your custom implementation
}
}
以下示例显示了扩展的自定义存储库的界面CrudRepository:
示例 33. 对存储库界面的更改
interface UserRepository extends CrudRepository, HumanRepository, ContactRepository {
// Declare query methods here
}
存储库可能由按声明顺序导入的多个自定义实现组成。自定义实现比基本实现和存储库方面具有更高的优先级。如果两个片段贡献相同的方法签名,则此排序允许您覆盖基本存储库和方面方法并解决歧义。存储库片段不限于在单个存储库界面中使用。多个存储库可以使用片段接口,让您可以在不同的存储库中重用自定义。
以下示例显示了存储库片段及其实现:
示例 34. 片段覆盖 save(…)
interface CustomizedSave {
S save(S entity);
}
class CustomizedSaveImpl implements CustomizedSave {
public S save(S entity) {
// Your custom implementation
}
}
以下示例显示了使用上述存储库片段的存储库:
示例 35. 自定义存储库接口
interface UserRepository extends CrudRepository, CustomizedSave {
}
interface PersonRepository extends CrudRepository, CustomizedSave {
}
如果您使用命名空间配置,存储库基础结构会尝试通过扫描它在其中找到存储库的包下的类来自动检测自定义实现片段。这些类需要遵循将命名空间元素的repository-impl-postfix属性附加到片段接口名称的命名约定。此后缀默认为Impl. 以下示例显示了一个使用默认后缀的存储库和一个为后缀设置自定义值的存储库:
示例 36. 配置示例
前面示例中的第一个配置尝试查找一个称为
com.acme.repository.CustomizedUserRepositoryImpl作为自定义存储库实现的类。第二个示例尝试查找com.acme.repository.CustomizedUserRepositoryMyPostfix.
如果在不同的包中找到多个具有匹配类名的实现,Spring Data 使用 bean 名称来标识使用哪个。
鉴于CustomizedUserRepository前面显示的以下两个自定义实现,使用第一个实现。它的 bean 名称是
customizedUserRepositoryImpl,它与片段 interface( CustomizedUserRepository) 加上后缀的名称相匹配Impl。
示例 37. 解决不明确的实现
package com.acme.impl.one;
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// Your custom implementation
}
package com.acme.impl.two;
@Component("specialCustomImpl")
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// Your custom implementation
}
如果您使用 注释UserRepository接口@Component("specialCustom"),那么 bean 名称加号Impl与 中为存储库实现定义的名称相匹配com.acme.impl.two,并使用它代替第一个名称。
内容提示:本文(Spring Data JPA 参考文档 )未完待续......