使用 Spring Data Repositories(上)
发布于 3 年前 作者 chaozeng 3827 次浏览 来自 分享

原标题:Spring认证|使用 Spring Data Repositories(上)

Spring Data repository 抽象的目标是显着减少为各种持久性存储实现数据访问层所需的样板代码量。

Spring Data 存储库文档和您的模块

本章解释了 Spring Data 存储库的核心概念和接口。本章中的信息来自 Spring Data Commons 模块。它使用 Java Persistence API (JPA) 模块的配置和代码示例。您应该将 XML 命名空间声明和要扩展的类型调整为您使用的特定模块的等效项。“命名空间参考”涵盖了所有支持存储库 API 的 Spring Data 模块都支持的 XML 配置。“存储库查询关键字”涵盖了存储库抽象支持的查询方法关键字。有关模块特定功能的详细信息,请参阅本文档中有关该模块的章节。

4.1. 核心概念

Spring Data 存储库抽象中的中心接口是Repository. 它需要域类来管理以及域类的 ID 类型作为类型参数。此接口主要用作标记接口,以捕获要使用的类型并帮助您发现扩展此接口的接口。该CrudRepository接口为正在管理的实体类提供复杂的 CRUD 功能。

示例 5.CrudRepository接口

public interface CrudRepository extends Repository {

S save(S entity);

Optional findById(ID primaryKey);

Iterable findAll();

long count();

void delete(T entity);

boolean existsById(ID primaryKey);

// … more functionality omitted.

保存给定的实体、返回由给定 ID 标识的实体、返回所有实体、返回实体的数量、删除给定的实体、指示具有给定 ID 的实体是否存在。

我们还提供特定于持久性技术的抽象,例如JpaRepository或MongoRepository。这些接口扩展CrudRepository,并露出下面的持久化技术在另外的能力,以比较通用的持久性与技术无关的接口,如CrudRepository。

在 之上CrudRepository,有一个PagingAndSortingRepository抽象,它添加了额外的方法来简化对实体的分页访问:

例 6.PagingAndSortingRepository界面

public interface PagingAndSortingRepository extends CrudRepository {

Iterable findAll(Sort sort);

Page findAll(Pageable pageable);

要访问User页面大小为 20的第二页,您可以执行以下操作:

PagingAndSortingRepository repository = // … get access to a bean

Page users = repository.findAll(PageRequest.of(1, 20));

除了查询方法,计数和删除查询的查询派生也是可用的。以下列表显示了派生计数查询的接口定义:

示例 7. 派生计数查询

interface UserRepository extends CrudRepository {

long countByLastname(String lastname);

}

以下清单显示了派生删除查询的接口定义:

示例 8. 派生删除查询

interface UserRepository extends CrudRepository {

long deleteByLastname(String lastname);

List removeByLastname(String lastname);

}

4.2. 查询方法

标准 CRUD 功能存储库通常对底层数据存储进行查询。使用 Spring Data,声明这些查询变成了一个四步过程:

声明一个扩展 Repository 或其子接口之一的接口,并将其键入它应该处理的域类和 ID 类型,如以下示例所示:

interface PersonRepository extends Repository { … }

在接口上声明查询方法。

interface PersonRepository extends Repository {

List findByLastname(String lastname);

}

设置 Spring 以使用JavaConfig或XML 配置为这些接口创建代理实例。

要使用 Java 配置,请创建一个类似于以下内容的类:

import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@EnableJpaRepositories

class Config { … }

要使用 XML 配置,请定义一个类似于以下内容的 bean:

本示例中使用了 JPA 命名空间。如果您对任何其他商店使用存储库抽象,则需要将其更改为商店模块的适当命名空间声明。换句话说,您应该交换jpa以支持,例如mongodb。

另请注意,JavaConfig 变体并未显式配置包,因为默认情况下使用带注释的类的包。要自定义要扫描的包,请使用basePackage…特定于数据存储的存储库的 -@Enable$Repositories注释的属性之一。

注入存储库实例并使用它,如以下示例所示:

class SomeClient { private final PersonRepository repository; SomeClient(PersonRepository repository) { this.repository = repository; } void doSomething() { List persons = repository.findByLastname(“Matthews”); }}

4.3. 定义存储库接口

要定义存储库接口,首先需要定义特定于域类的存储库接口。接口必须扩展Repository并键入域类和 ID 类型。如果要公开该域类型的 CRUD 方法,请使用扩展CrudRepository而不是Repository.

4.3.1. 微调存储库定义

通常情况下,你的资料库接口扩展Repository,CrudRepository或PagingAndSortingRepository。或者,如果您不想扩展 Spring Data 接口,也可以使用@RepositoryDefinition. 扩展CrudRepository公开了一套完整的方法来操作您的实体。如果您更愿意选择公开的方法,请将要公开的方法复制CrudRepository到域存储库中。

这样做可以让您在提供的 Spring Data Repositories 功能之上定义自己的抽象。

下面的示例示出了如何以选择性地露出CRUD方法(findById和save,在这种情况下):

示例 9. 有选择地公开 CRUD 方法

@NoRepositoryBean

interface MyBaseRepository extends Repository {

Optional findById(ID id);

S save(S entity);

}

interface UserRepository extends MyBaseRepository {

User findByEmailAddress(EmailAddress emailAddress);

}

在前面的例子,你定义为所有站点库一个共同的基础界面和暴露findById(…),以及save(…)。这些方法被发送到基础信息库实现你所选择的由Spring提供的数据(例如,如果使用JPA商店,实现是SimpleJpaRepository),因为它们匹配 中的方法签名CrudRepository。所以UserRepository现在可以保存用户,通过 ID 查找单个用户,并触发查询以Users通过电子邮件地址查找。

中间存储库接口用@NoRepositoryBean. 确保将该注释添加到 Spring Data 不应在运行时为其创建实例的所有存储库接口。

4.3.2. 使用具有多个 Spring 数据模块的存储库

在您的应用程序中使用唯一的 Spring Data 模块会使事情变得简单,因为定义范围内的所有存储库接口都绑定到 Spring Data 模块。有时,应用程序需要使用多个 Spring Data 模块。在这种情况下,存储库定义必须区分持久性技术。当在类路径上检测到多个存储库工厂时,Spring Data 进入严格的存储库配置模式。严格配置使用存储库或域类的详细信息来决定存储库定义的 Spring Data 模块绑定:

如果存储库定义扩展了特定于模块的存储库,则它是特定 Spring Data 模块的有效候选者。

如果域类使用特定于模块的类型注释进行注释,则它是特定 Spring Data 模块的有效候选者。Spring Data 模块接受第三方注解(例如 JPA’s @Entity)或提供自己的注解(例如@DocumentSpring Data MongoDB 和 Spring Data Elasticsearch)。

以下示例显示了使用特定于模块的接口(在本例中为 JPA)的存储库:

示例 10. 使用模块特定接口的存储库定义

interface MyRepository extends JpaRepository { }

@NoRepositoryBean

interface MyBaseRepository extends JpaRepository { … }

interface UserRepository extends MyBaseRepository { … }

MyRepository并在它们的类型层次结构中UserRepository扩展JpaRepository。它们是 Spring Data JPA 模块的有效候选者。

以下示例显示了使用通用接口的存储库:

示例 11. 使用通用接口的存储库定义

interface AmbiguousRepository extends Repository { … }

@NoRepositoryBean

interface MyBaseRepository extends CrudRepository { … }

interface AmbiguousUserRepository extends MyBaseRepository { … }

AmbiguousRepository和AmbiguousUserRepository仅延伸Repository,并CrudRepository在他们的类型层次。虽然这在使用唯一的 Spring Data 模块时很好,但多个模块无法区分这些存储库应该绑定到哪个特定的 Spring Data。

以下示例显示了使用带注释的域类的存储库:

示例 12. 使用带注释的域类的存储库定义

interface PersonRepository extends Repository { … }

@Entity

class Person { … }

interface UserRepository extends Repository { … }

@Document

class User { … }

PersonRepositoryReferences Person,它用 JPA@Entity注释进行了注释,所以这个存储库显然属于 Spring Data JPA。UserRepositoryReferences User,使用 Spring Data MongoDB 的@Document注解进行注解。

以下错误示例显示了使用具有混合注释的域类的存储库:

示例 13. 使用带有混合注释的域类的存储库定义

interface JpaPersonRepository extends Repository { … }

interface MongoDBPersonRepository extends Repository { … }

@Entity

@Document

class Person { … }

此示例显示使用 JPA 和 Spring Data MongoDB 注释的域类。它定义了两个存储库,JpaPersonRepository以及MongoDBPersonRepository. 一个用于 JPA,另一个用于 MongoDB。Spring Data 不再能够区分存储库,这会导致未定义的行为。

存储库类型详细信息和区分域类注释用于严格的存储库配置,以识别特定 Spring Data 模块的存储库候选者。在同一域类型上使用多个特定于持久性技术的注释是可能的,并且允许跨多个持久性技术重用域类型。但是,Spring Data 无法再确定与存储库绑定的唯一模块。

区分存储库的最后一种方法是确定存储库基础包的范围。基础包定义了扫描存储库接口定义的起点,这意味着存储库定义位于适当的包中。默认情况下,注解驱动的配置使用配置类的包。基于 XML 的配置中的基本包是必需的。

以下示例显示了基础包的注释驱动配置:

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

@EnableJpaRepositories(basePackages = “com.acme.repositories.jpa”)

@EnableMongoRepositories(basePackages = “com.acme.repositories.mongo”)

class Configuration { … }

4.4. 定义查询方法

存储库代理有两种方法可以从方法名称派生特定于商店的查询:

通过直接从方法名称派生查询。

通过使用手动定义的查询。

可用选项取决于实际商店。但是,必须有一个策略来决定创建什么实际查询。下一节描述了可用的选项。

4.4.1. 查询查找策略

以下策略可用于存储库基础结构来解析查询。通过 XML 配置,您可以通过query-lookup-strategy属性在命名空间配置策略。对于 Java 配置,您可以使用注解的queryLookupStrategy属性Enable$Repositories。特定数据存储可能不支持某些策略。

CREATE尝试从查询方法名称构造特定于商店的查询。一般的方法是从方法名称中删除一组给定的众所周知的前缀并解析方法的其余部分。您可以在“查询创建”中阅读有关查询构造的更多信息。

USE_DECLARED_QUERY尝试查找已声明的查询,如果找不到则抛出异常。查询可以由某个地方的注释定义或通过其他方式声明。请参阅特定商店的文档以查找该商店的可用选项。如果存储库基础结构在引导时未找到该方法的声明查询,则它会失败。

CREATE_IF_NOT_FOUND(默认)结合CREATE和USE_DECLARED_QUERY。它首先查找声明的查询,如果没有找到声明的查询,它会创建一个自定义的基于方法名称的查询。这是默认的查找策略,因此,如果您没有明确配置任何内容,就会使用它。它允许按方法名称快速定义查询,还允许通过根据需要引入声明的查询来自定义这些查询。

4.4.2. 查询创建

Spring Data 存储库基础结构中内置的查询构建器机制对于构建对存储库实体的约束查询非常有用。

以下示例显示了如何创建多个查询:

示例 15. 从方法名称创建查询

interface PersonRepository extends Repository {

List findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

// Enables the distinct flag for the query

List findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);

List findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

// Enabling ignoring case for an individual property

List findByLastnameIgnoreCase(String lastname);

// Enabling ignoring case for all suitable properties

List findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

// Enabling static ORDER BY for a query

List findByLastnameOrderByFirstnameAsc(String lastname);

List findByLastnameOrderByFirstnameDesc(String lastname);

}

解析查询方法名称分为主语和谓语。第一部分 ( find…By, exists…By) 定义查询的主题,第二部分构成谓词。介绍从句(主语)可以包含进一步的表达。find(或其他引入关键字)和之间的任何文本都By被认为是描述性的,除非使用结果限制关键字之一,例如Distinct在要创建的查询上设置不同的标志或Top/First以限制查询结果。

附录包含查询方法主题关键字和查询方法谓词关键字的完整列表,包括排序和字母大小写修饰符。但是,第一个By充当分隔符以指示实际条件谓词的开始。在非常基础的层面上,您可以定义实体属性的条件并将它们与And和连接起来Or。

解析方法的实际结果取决于您为其创建查询的持久性存储。但是,有一些一般的事情需要注意:

表达式通常是与可以连接的运算符相结合的属性遍历。您可以将属性表达式与AND和结合使用OR。您还可以得到这样的运营商为支撑Between,LessThan,GreaterThan,和Like该属性的表达式。受支持的运算符可能因数据存储而异,因此请参阅参考文档的相应部分。

方法解析器支持IgnoreCase为单个属性(例如findByLastnameIgnoreCase(…))或支持忽略大小写的类型(通常是String实例,例如findByLastnameAndFirstnameAllIgnoreCase(…))的所有属性设置标志。是否支持忽略大小写可能因商店而异,因此请参阅参考文档中的相关部分以了解商店特定的查询方法。

您可以通过将OrderBy子句附加到引用属性的查询方法并提供排序方向(Asc或Desc)来应用静态排序。要创建支持动态排序的查询方法,请参阅“特殊参数处理”。

4.4.3. 属性表达式

属性表达式只能引用托管实体的直接属性,如前面的示例所示。在创建查询时,您已经确保解析的属性是托管域类的属性。但是,您也可以通过遍历嵌套属性来定义约束。考虑以下方法签名:

List findByAddressZipCode(ZipCode zipCode);

假设 aPerson有Address一个ZipCode。在这种情况下,该方法会创建x.address.zipCode属性遍历。解析算法首先将整个部分 ( AddressZipCode)解释为属性并检查具有该名称(未大写)的属性的域类。如果算法成功,它将使用该属性。如果不是,则算法将来自右侧的驼峰式部分的源分成头部和尾部,并尝试找到相应的属性 - 在我们的示例中,AddressZip和Code。如果算法找到具有该头部的属性,它会取尾部并继续从那里向下构建树,以刚才描述的方式将尾部拆分。如果第一个分割不匹配,算法将分割点向左移动 ( Address,ZipCode) 并继续。

尽管这应该适用于大多数情况,但算法可能会选择错误的属性。假设这个Person类也有一个addressZip属性。该算法将在第一个分割轮中匹配,选择错误的属性,并失败(因为 的类型addressZip可能没有code属性)。

要解决这种歧义,您可以_在方法名称中使用手动定义遍历点。所以我们的方法名称如下:

List findByAddress_ZipCode(ZipCode zipCode);

因为我们将下划线字符视为保留字符,所以我们强烈建议遵循标准的 Java 命名约定(即,不要在属性名称中使用下划线,而是使用驼峰式大小写)。

4.4.4. 特殊参数处理

要处理查询中的参数,请定义方法参数,如前面示例中所示。除此之外,基础设施识别某些特定类型,如Pageable和Sort,以动态地将分页和排序应用于您的查询。以下示例演示了这些功能:

示例 16.在查询方法中使用Pageable、Slice和Sort

Page findByLastname(String lastname, Pageable pageable);

Slice findByLastname(String lastname, Pageable pageable);

List findByLastname(String lastname, Sort sort);

List findByLastname(String lastname, Pageable pageable);

API 接受Sort并Pageable期望将非null值传递给方法。如果您不想应用任何排序或分页,请使用Sort.unsorted()和Pageable.unpaged()。

第一种方法允许您将org.springframework.data.domain.Pageable实例传递给查询方法,以将分页动态添加到静态定义的查询中。APage知道可用的元素和页面的总数。它通过基础结构触发计数查询来计算总数。由于这可能很昂贵(取决于使用的商店),您可以改为返回Slice. ASlice只知道下一个Slice是否可用,这在遍历更大的结果集时可能就足够了。

排序选项也通过Pageable实例处理。如果您只需要排序,请org.springframework.data.domain.Sort在您的方法中添加一个参数。如您所见,返回 aList也是可能的。在这种情况下,Page不会创建构建实际实例所需的额外元数据(这意味着不会发出本来需要的额外计数查询)。相反,它限制查询仅查找给定范围的实体。

要了解整个查询获得了多少页,您必须触发额外的计数查询。默认情况下,此查询源自您实际触发的查询。

分页和排序

您可以使用属性名称定义简单的排序表达式。您可以连接表达式以将多个条件收集到一个表达式中。

示例 17. 定义排序表达式

Sort sort = Sort.by(“firstname”).ascending()

.and(Sort.by(“lastname”).descending());

对于定义排序表达式的更类型安全的方法,从定义排序表达式的类型开始,并使用方法引用来定义排序的属性。

示例 18. 使用类型安全 API 定义排序表达式

TypedSort person = Sort.sort(Person.class);

Sort sort = person.by(Person::getFirstname).ascending()

.and(person.by(Person::getLastname).descending());

TypedSort.by(…) 通过(通常)使用 CGlib 来使用运行时代理,这可能会在使用 Graal VM Native 等工具时干扰本机映像编译。

如果您的商店实现支持 Querydsl,您还可以使用生成的元模型类型来定义排序表达式:

示例 19. 使用 Querydsl API 定义排序表达式

QSort sort = QSort.by(QPerson.firstname.asc())

.and(QSort.by(QPerson.lastname.desc()));

内容来源:(Spring中国教育管理中心)

使用 Spring Data Repositories,未完待续…

回到顶部