原标题:Spring认证中国教育管理中心-Spring Data Neo4j教程六(Spring中国教育管理中心)
Spring认证:Spring Data Neo4j教程六
7.4.7.存储库方法的空处理
从 Spring Data 2.0 开始,返回单个聚合实例的存储库 CRUD 方法使用 Java 8Optional来指示可能缺少值。除此之外,Spring Data 支持在查询方法上返回以下包装类型:
com.google.common.base.Optional
scala.Option
io.vavr.control.Option
或者,查询方法可以选择根本不使用包装器类型。然后通过返回来指示不存在查询结果null。返回集合、集合替代、包装器和流的存储库方法保证永远不会返回null,而是返回相应的空表示。详见“附录 B ”。
可空性注释
您可以使用Spring Framework 的可空性注释来表达存储库方法的可空性约束。它们在运行时提供了一种工具友好的方法和选择加入null检查,如下所示:
@NonNullApi:在包级别上用于声明参数和返回值的默认行为分别是既不接受也不产生null值。
@NonNull: 用于不能使用的参数或返回值null(在适用的情况下不需要用于参数和返回值@NonNullApi)。
@Nullable: 用在参数或返回值上即可null。
Spring 注释使用JSR 305注释(一种休眠但广泛使用的 JSR)进行元注释。JSR 305 元注释让工具供应商(例如IDEA、Eclipse和Kotlin)以通用方式提供空安全支持,而无需对 Spring 注释进行硬编码支持。要为查询方法启用可空性约束的运行时检查,您需要使用 Spring 的@NonNullApiin在包级别激活不可空性package-info.java,如下例所示:
示例 30. 在 package-info.java
@org.springframework.lang.NonNullApi
package com.acme;
一旦非空默认设置到位,存储库查询方法调用将在运行时验证可空性约束。如果查询结果违反了定义的约束,则会引发异常。当方法将返回null但被声明为不可为空(默认情况下,在存储库所在的包上定义注释)时,就会发生这种情况。如果您想再次选择可空结果,请有选择地使用@Nullable单个方法。使用本节开头提到的结果包装类型继续按预期工作:空结果被转换为表示缺席的值。
以下示例显示了刚刚描述的一些技术:
示例 31. 使用不同的可空性约束
package com.acme;
import org.springframework.lang.Nullable;
interface UserRepository extends Repository<User, Long> {
User getByEmailAddress(EmailAddress emailAddress);
@Nullable
User findByEmailAddress(@Nullable EmailAddress emailAdress);
Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress);
}
存储库位于我们为其定义了非空行为的包(或子包)中。
EmptyResultDataAccessException当查询不产生结果时抛出一个。IllegalArgumentException当emailAddress交给方法时抛出一个null。
null当查询没有产生结果时返回。也接受null作为 的值emailAddress。
Optional.empty()当查询没有产生结果时返回。IllegalArgumentException当emailAddress交给方法时抛出一个null。
Spring认证:Spring Data Neo4j教程六
基于 Kotlin 的存储库中的可空性
Kotlin 将可空性约束的定义融入到语言中。Kotlin 代码编译为字节码,它不通过方法签名表达可空性约束,而是通过编译的元数据。确保kotlin-reflect在您的项目中包含 JAR 以启用对 Kotlin 可空性约束的自省。Spring Data 存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:
示例 32. 在 Kotlin 存储库上使用可空性约束
interface UserRepository : Repository<User, String> {
fun findByUsername(username: String): User
fun findByFirstname(firstname: String?): User?
}
该方法将参数和结果都定义为不可为空(Kotlin 默认)。Kotlin 编译器拒绝传递给方法的方法调用null。如果查询产生空结果,
EmptyResultDataAccessException则抛出 an。
此方法接受null参数firstname并null在查询未产生结果时返回。
7.4.8流式查询结果
Stream<T>您可以使用 Java 8作为返回类型以增量方式处理查询方法的结果。不是将查询结果包装在 a 中Stream,而是使用特定于数据存储的方法来执行流式传输,如以下示例所示:
示例 33. 使用 Java 8 流式传输查询结果 Stream<T>
@Query(“select u from User u”)
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query(“select u from User u”)
Stream<User> streamAllPaged(Pageable pageable);
AStream可能包装底层数据存储特定的资源,因此必须在使用后关闭。Stream您可以使用该close()方法或使用 Java 7块手动关闭try-with-resources,如以下示例所示:
示例 34. 处理块Stream<T>中的结果try-with-resources
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
并非所有 Spring Data 模块当前都支持Stream<T>作为返回类型。
7.4.9异步查询结果
您可以使用Spring 的异步方法运行能力异步运行存储库查询。这意味着该方法在调用时立即返回,而实际查询发生在已提交给 Spring 的任务中TaskExecutor。异步查询与响应式查询不同,不应混用。有关响应式支持的更多详细信息,请参阅特定于商店的文档。以下示例显示了许多异步查询:
@Async
Future<User> findByFirstname(String firstname);
@Async
CompletableFuture<User> findOneByFirstname(String firstname);
@Async
ListenableFuture<User> findOneByLastname(String lastname);
用作
java.util.concurrent.Future返回类型。
使用 Java
8java.util.concurrent.CompletableFuture作为返回类型。
使用
aorg.springframework.util.concurrent.ListenableFuture作为返回类型。
7.5创建存储库实例
本节介绍如何为已定义的存储库接口创建实例和 bean 定义。一种方法是使用每个支持存储库机制的 Spring Data 模块附带的 Spring 命名空间,尽管我们通常建议使用 Java 配置。
7.5.1.XML 配置
每个 Spring Data 模块都包含一个repositories元素,可让您定义 Spring 为您扫描的基本包,如以下示例所示:
示例 35. 通过 XML 启用 Spring Data 存储库
<?xml version=“1.0” encoding=“UTF-8”?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation=“http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd”>
<repositories base-package=“com.acme.repositories” />
</beans:beans>
在前面的示例中,指示 Spring 扫描com.acme.repositories其所有子包以查找扩展接口Repository或其子接口之一。对于找到的每个接口,基础设施都会注册特定的持久性技术,FactoryBean以创建适当的代理来处理查询方法的调用。每个 bean 都在从接口名称派生的 bean 名称下注册,因此接口UserRepository将在userRepository. 嵌套存储库接口的 Bean 名称以它们的封闭类型名称为前缀。该base-package属性允许使用通配符,以便您可以定义扫描包的模式。
使用过滤器
默认情况下,基础设施会选择扩展Repository位于已配置基本包下的特定于持久性技术的子接口的每个接口,并为其创建一个 bean 实例。但是,您可能希望更细粒度地控制哪些接口具有为其创建的 bean 实例。为此,请在元素内使用<include-filter />和元素。语义完全等同于 Spring 上下文命名空间中的元素。有关详细信息,请参阅这些元素的Spring 参考文档。<exclude-filter /><repositories />
例如,要从实例化中排除某些接口作为存储库 bean,您可以使用以下配置:
示例 36. 使用 exclude-filter 元素
<repositories base-package=“com.acme.repositories”>
<context:exclude-filter type=“regex” expression=".*SomeRepository" />
</repositories>
前面的示例排除了所有以SomeRepository实例化结尾的接口。
7.5.2.Java 配置
您还可以通过在 Java 配置类上使用特定于存储的@Enable${store}Repositories注释来触发存储库基础结构。有关 Spring 容器的基于 Java 的配置的介绍,请参阅Spring 参考文档中的 JavaConfig。
启用 Spring Data 存储库的示例配置类似于以下内容:
示例 37.基于注释的存储库配置示例
@Configuration
@EnableJpaRepositories(“com.acme.repositories”)
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
前面的示例使用特定于 JPA 的注释,您可以根据实际使用的存储模块对其进行更改。这同样适用于EntityManagerFactorybean 的定义。请参阅涵盖商店特定配置的部分。
7.5.3.独立使用
您还可以在 Spring 容器之外使用存储库基础架构——例如,在 CDI 环境中。您的类路径中仍然需要一些 Spring 库,但通常您也可以通过编程方式设置存储库。提供存储库支持的 Spring Data 模块附带了特定RepositoryFactory于您可以使用的持久性技术,如下所示:
示例 38. 存储库工厂的独立使用
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
7.6.Spring Data Repository 的自定义实现
Spring Data 提供了各种选项来创建几乎不需要编码的查询方法。但是当这些选项不能满足您的需求时,您还可以为存储库方法提供您自己的自定义实现。本节介绍如何执行此操作。
7.6.1.自定义单个存储库
要使用自定义功能丰富存储库,您必须首先为自定义功能定义片段接口和实现,如下所示:
示例 39. 自定义存储库功能的接口
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
示例 40. 自定义存储库功能的实现
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
与片段接口对应的类名中最重要的部分是Impl后缀。
实现本身不依赖于 Spring Data,可以是常规的 Spring bean。因此,您可以使用标准的依赖注入行为来注入对其他 bean 的引用(例如 a JdbcTemplate),参与切面等等。
然后可以让你的repository接口扩展fragment接口,如下:
示例 41. 对存储库界面的更改
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
使用存储库接口扩展片段接口结合了 CRUD 和自定义功能,并使其可供客户端使用。
Spring Data 存储库是通过使用构成存储库组合的片段来实现的。片段是基础存储库、功能方面(例如QueryDsl)和自定义接口及其实现。每次将接口添加到存储库接口时,都会通过添加片段来增强组合。基本存储库和存储库方面的实现由每个 Spring Data 模块提供。
以下示例显示了自定义接口及其实现:
示例 42. 片段及其实现
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:
Spring认证:Spring Data Neo4j教程六
示例 43. 对存储库界面的更改
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}
存储库可能由多个按其声明顺序导入的自定义实现组成。自定义实现具有比基本实现和存储库方面更高的优先级。此排序允许您覆盖基本存储库和方面方法,并在两个片段贡献相同的方法签名时解决歧义。存储库片段不限于在单个存储库界面中使用。多个存储库可以使用一个片段接口,让您可以在不同的存储库中重用自定义。
以下示例显示了存储库片段及其实现:
示例 44. 片段覆盖 save(…)
interface CustomizedSave<T> {
<S extends T> S save(S entity);
}
class CustomizedSaveImpl<T> implements CustomizedSave<T> {
public <S extends T> S save(S entity) {
// Your custom implementation
}
}
以下示例显示了使用上述存储库片段的存储库:
示例 45. 自定义存储库接口
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}
interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
配置
如果您使用命名空间配置,存储库基础结构会尝试通过扫描找到存储库的包下的类来自动检测自定义实现片段。这些类需要遵循将命名空间元素的repository-impl-postfix属性附加到片段接口名称的命名约定。此后缀默认为Impl. 以下示例显示了使用默认后缀的存储库和为后缀设置自定义值的存储库:
示例 46. 配置示例
<repositories base-package=“com.acme.repository” />
<repositories base-package=“com.acme.repository” repository-impl-postfix=“MyPostfix” />
前面示例中的第一个配置尝试查找一个称为
com.acme.repository.CustomizedUserRepositoryImpl作为自定义存储库实现的类。第二个示例尝试查找com.acme.repository.CustomizedUserRepositoryMyPostfix。
歧义的解决
如果在不同的包中找到多个具有匹配类名的实现,Spring Data 会使用 bean 名称来识别要使用哪一个。
鉴于CustomizedUserRepository前面所示的以下两个自定义实现,使用第一个实现。它的 bean 名称
customizedUserRepositoryImpl与片段 interface( CustomizedUserRepository) 加上后缀的名称相匹配Impl。
示例 47. 歧义实现的解决
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 名称 plusImpl与 中为存储库实现定义的名称相匹配com.acme.impl.two,并且使用它而不是第一个。
手动接线
如果您的自定义实现仅使用基于注释的配置和自动装配,那么前面显示的方法效果很好,因为它被视为任何其他 Spring bean。如果你的实现片段 bean 需要特殊的连接,你可以声明这个 bean 并根据上一节中描述的约定命名它。然后,基础设施按名称引用手动定义的 bean 定义,而不是自己创建一个。以下示例显示了如何手动连接自定义实现:
示例 48. 自定义实现的手动接线
<repositories base-package=“com.acme.repository” />
<beans:bean id=“userRepositoryImpl” class="…">
<!-- further configuration -->
</beans:bean>