3.3 定义库接口

3.3 定义库接口

3.3. Defining repository interfaces

首先你需要定义一个指定域类的库接口。接口必须继承Repository并指定域类和ID的类型。如果你希望暴露CRUD方法,将继承Repository更换为CrudRepository即可。

As a first step you define a domain class-specific repository interface. The interface must extend Repository and be typed to the domain class and an ID type. If you want to expose CRUD methods for that domain type, extend CrudRepository instead of Repository.

3.3.1 微调库定义

3.3.1. Fine-tuning repository definition

一般你的库接口会继承Repository, CrudRepository或者PagingAndSortingRepository。如果你不想继承Spring Data的接口,还有一种做法是将你的库接口注解为@RepositoryDefinition。继承CrudRepository将暴露一系列的操作实体的方法。如果你希望选择性的暴露这些方法,可以将想要暴露的方法从CrudRepository复制到你的域库中。

Typically, your repository interface will extend Repository, CrudRepository or PagingAndSortingRepository. Alternatively, if you do not want to extend Spring Data interfaces, you can also annotate your repository interface with @RepositoryDefinition. Extending CrudRepository exposes a complete set of methods to manipulate your entities. If you prefer to be selective about the methods being exposed, simply copy the ones you want to expose from CrudRepository into your domain repository.

例7. 选择性暴露CRUD方法

Example 7. Selectively exposing CRUD methods

@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {

  Optional<T> findById(ID id);

  <S extends T> S save(S entity);
}3.

interface UserRepository extends MyBaseRepository<User, Long> {
  User findByEmailAddress(EmailAddress emailAddress);
}

在此步骤中,为所有的域类定义了一个通用的基础接口,并暴露了findById(…)save(…)方法。这些方法将被导向你在Spring Data中选择的基础库实现,例如JPA SimpleJpaRepository,因为其方法匹配了CrudRepository中的方法。所以UserRepository提供保存用户和通过id查询单个用户的功能,还提供了通过email地址查询Users的功能。

In this first step you defined a common base interface for all your domain repositories and exposed findById(…) as well as save(…).These methods will be routed into the base repository implementation of the store of your choice provided by Spring Data ,e.g. in the case if JPA SimpleJpaRepository, because they are matching the method signatures in CrudRepository. So the UserRepository will now be able to save users, and find single ones by id, as well as triggering a query to find Users by their email address.

需要注意此库接口注解为@NoRepositoryBean。确保你在所有库接口上都使用了此注解,则Spring Data在运行时不会创建这些实例。

Note, that the intermediate repository interface is annotated with @NoRepositoryBean. Make sure you add that annotation to all repository interfaces that Spring Data should not create instances for at runtime.

3.3.2 库方法的空操作

3.3.2. Null handling of repository methods

从Spring Data 2.0开始,返回单个聚合实例的库CRUD方法使用了Java 8的Optional来表明一个值可能为空。除此之外,Spring Data还支持查询方法返回其他的封装类型:

As of Spring Data 2.0, repository CRUD methods that return an individual aggregate instance use Java 8’s Optional to indicate the potential absence of a value. Besides that, Spring Data supports to return other wrapper types on query methods:

  • com.google.common.base.Optional
  • scala.Option
  • io.vavr.control.Option
  • javaslang.control.Option (deprecated as Javaslang is deprecated)

另一种查询方法是可以选择不使用任何封装类型。缺失的查询结果可以定义为返回null。库方法返回集合、备选集合、封装以及流来确保返回null但并不是空响应。更多细节参考库查询返回类型

Alternatively query methods can choose not to use a wrapper type at all. The absence of a query result will then be indicated by returning null. Repository methods returning collections, collection alternatives, wrappers, and streams are guaranteed never to return null but rather the corresponding empty representation. See Repository query return types for details.

可空注解

Nullability annotations

你可以使用[Spring框架的可空注解]来为库方法解释其可空限制。(https://docs.spring.io/spring/docs/5.0.3.RELEASE/spring-framework-reference/core.html#null-safety)。框架提供了一个方便的方法并在运行时可选以下null检查:

You can express nullability constraints for repository methods using Spring Framework’s nullability annotations. They provide a tooling-friendly approach and opt-in null checks during runtime:

  • @NonNullApi – 在包级别声明为参数和返回值默认的行为是不接受和返回null值。
  • @NonNull – 参数或者返回值不能为null(当@NonNullApi起效的地方无需此注解)。
  • @Nullable – 参数或者返回值可以为null
  • @NonNullApi – to be used on the package level to declare that the default behavior for parameters and return values is to not accept or produce null values.
  • @NonNull – to be used on a parameter or return value that must not be null (not needed on parameter and return value where @NonNullApi applies).
  • @Nullable – to be used on a parameter or return value that can be null.

Spring annotations are meta-annotated with JSR 305 annotations (a dormant but widely spread JSR). JSR 305 meta-annotations allow tooling vendors like IDEA, Eclipse, or Kotlin to provide null-safety support in a generic way, without having to hard-code support for Spring annotations. To enable runtime checking of nullability constraints for query methods, you need to activate non-nullability on package level using Spring’s @NonNullApi in package-info.java:

例8. 在package-info.java中声明非空性

Example 8. Declaring non-nullability in package-info.java

@org.springframework.lang.NonNullApi
package com.acme;

一旦在此处设置了默认非空,库查询方法的调用将在运行时验证非空限制。当一个查询执行结果与定义的限制不一致时将抛出异常,例如已经定义了非空,但方法因某些原因返回null。如果还想让结果可空,可在方法上使用@Nullable。使用前面提到的结果封装类型可以使结果按预期执行,例如空结果将会被转换为表示空的值。

Once non-null defaulting is in place, repository query method invocations will get validated at runtime for nullability constraints. Exceptions will be thrown in case a query execution result violates the defined constraint, i.e. the method would return null for some reason but is declared as non-nullable (the default with the annotation defined on the package the repository resides in). If you want to opt-in to nullable results again, selectively use @Nullable that a method. Using the aforementioned result wrapper types will continue to work as expected, i.e. an empty result will be translated into the value representing absence.

例9. 使用不同的可空限制

Example 9. Using different nullability constraints

package com.acme;                                                       // 1

import org.springframework.lang.Nullable;

interface UserRepository extends Repository<User, Long> {

  User getByEmailAddress(EmailAddress emailAddress);                    // 2

  @Nullable
  User findByEmailAddress(@Nullable EmailAddress emailAdress);          // 3

  Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); // 4
}
  1. 这个库类属于之前我们定义的非空行为的一个包(或者子包)(在上一个例子中)。
  2. 当查询执行后没有返回一个结果时,将抛出一个EmptyResultDataAccessException。当传入方法的emailAddress参数为null时,将抛出IllegalArgumentException
  3. 当查询执行后没有返回一个结果时,将返回null。同样对于入参emailAddress也允许为null
  4. 当查询执行后没有返回一个结果时,将返回Optional.empty()。入参emailAddressnull时,将抛出IllegalArgumentException
  1. The repository resides in a package (or sub-package) for which we’ve defined non-null behavior (see above).
  2. Will throw an EmptyResultDataAccessException in case the query executed does not produce a result. Will throw an IllegalArgumentException in case the emailAddress handed to the method is null.
  3. Will return null in case the query executed does not produce a result. Also accepts null as value for emailAddress.
  4. Will return Optional.empty() in case the query executed does not produce a result. Will throw an IllegalArgumentException in case the emailAddress handed to the method is null.

3.3.3 对多个Spring Data模块使用库

3.3.3. Using Repositories with multiple Spring Data modules

在应用中使用一个Spring Data模块比较简单,因此在定义域中所有的库接口都绑定到这个Spring Data模块。但有时候应用需要使用多个Spring Data模块。在这种情况下,库定义就需要在不同的持久化逻辑中加以区别。Spring Data严格限制了库配置模式,因为需要在类路径下检测多个库工厂。严格配置需要库详情或者域类来决定绑定到库定义上的Spring Data模块:

Using a unique Spring Data module in your application makes things simple hence, all repository interfaces in the defined scope are bound to the Spring Data module. Sometimes applications require using more than one Spring Data module. In such case, it’s required for a repository definition to distinguish between persistence technologies. Spring Data enters strict repository configuration mode because it detects multiple repository factories on the class path. Strict configuration requires details on the repository or the domain class to decide about Spring Data module binding for a repository definition:

  1. 如果库定义继承了指定模块库,则其成为特定Spring Data模块的合法备选。
  2. 如果域类注解了指定模块类型注解,则其成为特定Spring Data模块的合法备选。Spring Data模块接受第三方注解(例如JPA的@Entity),也可以提供自己的注解,例如Spring Data MongoDB/Spring Data Elasticsearch的@Document
  1. If the repository definition extends the module-specific repository, then it’s a valid candidate for the particular Spring Data module.
  2. If the domain class is annotated with the module-specific type annotation, then it’s a valid candidate for the particular Spring Data module. Spring Data modules accept either 3rd party annotations (such as JPA’s @Entity) or provide own annotations such as @Document for Spring Data MongoDB/Spring Data Elasticsearch.

例11. 使用指定模块接口的库定义

Example 11. Repository definitions using Module-specific Interfaces

interface MyRepository extends JpaRepository<User, Long> { }

@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
  …
}

interface UserRepository extends MyBaseRepository<User, Long> {
  …
}

MyRepositoryUserRepository继承了JpaRepository。则他们为Spring Data JPA模块的合法备选。

MyRepository and UserRepository extend JpaRepository in their type hierarchy. They are valid candidates for the Spring Data JPA module.

例12. 使用泛型接口的库定义

Example 12. Repository definitions using generic Interfaces

interface AmbiguousRepository extends Repository<User, Long> {
 …
}

@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
  …
}

interface AmbiguousUserRepository extends MyBaseRepository<User, Long> {
  …
}

AmbiguousRepositoryAmbiguousUserRepository只是继承了RepositoryCrudRepository。使用单一Spring Data模块时没有问题,多个模块则无法区分这些库需要绑定到哪个Spring Data上。

AmbiguousRepository and AmbiguousUserRepository extend only Repository and CrudRepository in their type hierarchy. While this is perfectly fine using a unique Spring Data module, multiple modules cannot distinguish to which particular Spring Data these repositories should be bound.

例13. 使用注解域类的库定义

Example 13. Repository definitions using Domain Classes with Annotations

interface PersonRepository extends Repository<Person, Long> {
 …
}

@Entity
class Person {
  …
}

interface UserRepository extends Repository<User, Long> {
 …
}

@Document
class User {
  …
}

PersonRepository对应的Person使用了JPA注解@Entity,所以此库是属于Spring Data JPA。UserRepository对应的User使用了Spring Data MongoDB的@Document注解。

PersonRepository references Person which is annotated with the JPA annotation @Entity so this repository clearly belongs to Spring Data JPA. UserRepository uses User annotated with Spring Data MongoDB’s @Document annotation.

例14. 使用混合注解域类的库定义

Example 14. Repository definitions using Domain Classes with mixed Annotations

interface JpaPersonRepository extends Repository<Person, Long> {
 …
}

interface MongoDBPersonRepository extends Repository<Person, Long> {
 …
}

@Entity
@Document
class Person {
  …
}

这个例子展示了同时使用JPA和Spring Data MongoDB注解的一个域类。定义了两个库JpaPersonRepositoryMongoDBPersonRepository。一个为了JPA另一个为了MongoDB。Spring Data也不需要分别使用哪个。

This example shows a domain class using both JPA and Spring Data MongoDB annotations. It defines two repositories, JpaPersonRepository and MongoDBPersonRepository. One is intended for JPA and the other for MongoDB usage. Spring Data is no longer able to tell the repositories apart which leads to undefined behavior.

库类型详情定义域类注解都用于限制库配置和为特定Spring Data模块定义库备选。在一个域类型上使用多个持久化技术注解将在多个持久化技术中重用域类型,但Spring Data无需决定绑定到库的唯一模块。

Repository type details and identifying domain class annotations are used for strict repository configuration identify repository candidates for a particular Spring Data module. Using multiple persistence technology-specific annotations on the same domain type is possible to reuse domain types across multiple persistence technologies, but then Spring Data is no longer able to determine a unique module to bind the repository.

区别库的最后一个方法是定义库的基础包。基础包定义了扫描库接口定义的起始点,意味着从合适的包中搜索库定义。默认注解驱动配置使用配置类的包。基于XML的配置的基础包是必须的。

The last way to distinguish repositories is scoping repository base packages. Base packages define the starting points for scanning for repository interface definitions which implies to have repository definitions located in the appropriate packages. By default, annotation-driven configuration uses the package of the configuration class. The base package in XML-based configuration is mandatory.

例15. 基础包的注解驱动配置

Example 15. Annotation-driven configuration of base packages

@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
interface Configuration { }

翻译部分版权归YahaCode团队所有。仅供学习研究之用,任何组织或个人不得私自以此用于任何形式的商业目的

发表评论