2. 使用 Spring 数据存储库
Spring Data 存储库抽象的目标是显著减少为各种持久性存储实现数据访问层所需的样板代码量。
Spring Data 存储库文档和您的模块 |
2.1. 核心概念
Spring Data 存储库抽象中的中心接口是 。
它需要 domain 类来管理,并将 domain 类的标识符类型作为类型参数。
此接口主要用作标记接口,用于捕获要使用的类型,并帮助您发现扩展此接口的接口。
CrudRepository
接口为正在管理的实体类提供复杂的 CRUD 功能。Repository
CrudRepository
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity); (1)
Optional<T> findById(ID primaryKey); (2)
Iterable<T> findAll(); (3)
long count(); (4)
void delete(T entity); (5)
boolean existsById(ID primaryKey); (6)
// … more functionality omitted.
}
1 | 保存给定的实体。 |
2 | 返回由给定 ID 标识的实体。 |
3 | 返回所有实体。 |
4 | 返回实体数。 |
5 | 删除给定的实体。 |
6 | 指示是否存在具有给定 ID 的实体。 |
此接口中声明的方法通常称为 CRUD 方法。
我们还提供特定于持久性技术的抽象,例如 或 。
这些接口扩展并公开了底层持久化技术的功能,以及相当通用的与持久化技术无关的接口,例如。JpaRepository MongoRepository CrudRepository CrudRepository |
在 之上,还有一个 PagingAndSortingRepository
抽象,它添加了其他方法以简化对实体的分页访问:CrudRepository
PagingAndSortingRepository
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
要按页面大小 20 访问第二个页面,您可以执行以下操作:User
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(PageRequest.of(1, 20));
除了查询方法之外,还可以使用 count 和 delete 查询的查询派生。 以下列表显示了派生计数查询的接口定义:
interface UserRepository extends CrudRepository<User, Long> {
long countByLastname(String lastname);
}
下面的清单显示了派生的 delete 查询的接口定义:
interface UserRepository extends CrudRepository<User, Long> {
long deleteByLastname(String lastname);
List<User> removeByLastname(String lastname);
}
2.2. 查询方法
标准 CRUD 功能存储库通常对底层数据存储进行查询。 使用 Spring Data,声明这些查询将成为一个四步过程:
-
声明一个扩展 Repository 的接口或其子接口之一,并将其键入到它应该处理的域类和 ID 类型,如以下示例所示:
interface PersonRepository extends Repository<Person, Long> { … }
-
在接口上声明查询方法。
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
-
设置 Spring 以使用 JavaConfig 或 XML 配置为这些接口创建代理实例。
-
要使用 Java 配置,请创建一个类似于以下内容的类:
@EnableJpaRepositories class Config { … }
-
要使用 XML 配置,请定义类似于以下内容的 Bean:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="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"> <jpa:repositories base-package="com.acme.repositories"/> </beans>
此示例中使用 JPA 命名空间。 如果你对任何其他 store 使用仓库抽象,你需要将其更改为 store 模块的相应命名空间声明。 换句话说,您应该交换以支持例如 .
jpa
mongodb
另外,请注意,JavaConfig 变体不会显式配置包,因为默认情况下使用带 Comments 的类的包。 要自定义要扫描的软件包,请使用数据存储特定存储库的 -annotation 的属性之一。
basePackage…
@Enable${store}Repositories
-
-
注入存储库实例并使用它,如以下示例所示:
class SomeClient { private final PersonRepository repository; SomeClient(PersonRepository repository) { this.repository = repository; } void doSomething() { List<Person> persons = repository.findByLastname("Matthews"); } }
以下各节详细介绍了每个步骤:
2.3. 定义存储库接口
要定义存储库接口,您首先需要定义特定于域类的存储库接口。
接口必须扩展并键入域类和 ID 类型。
如果要公开该域类型的 CRUD 方法,请使用 extend 而不是 .Repository
CrudRepository
Repository
2.3.1. 微调 repository 定义
通常,您的存储库接口扩展 、 或 .
或者,如果您不想扩展 Spring Data 接口,也可以使用 .
扩展 公开了一整套用于操作实体的方法。
如果您希望对公开的方法有选择性,请将要从中公开的方法复制到域存储库中。Repository
CrudRepository
PagingAndSortingRepository
@RepositoryDefinition
CrudRepository
CrudRepository
这样做可以让您在提供的 Spring Data Repositories 功能之上定义自己的抽象。 |
以下示例显示如何选择性地公开 CRUD 方法(在本例中为 和 ):findById
save
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends Repository<T, ID> {
Optional<T> findById(ID id);
<S extends T> S save(S entity);
}
interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(EmailAddress emailAddress);
}
在前面的示例中,您为所有域存储库定义了一个通用的基本接口,并公开了 .这些方法将路由到 Spring Data 提供的所选存储的基本存储库实现中(例如,如果使用 JPA,则实现为 ),因为它们与 中的方法签名匹配。
因此,现在可以保存用户,按 ID 查找单个用户,并触发查询以按电子邮件地址查找。findById(…)
save(…)
SimpleJpaRepository
CrudRepository
UserRepository
Users
中间存储库接口用 .
确保将该 Comments 添加到 Spring Data 不应在运行时为其创建实例的所有存储库接口。@NoRepositoryBean |
2.3.2. 使用具有多个 Spring Data 模块的存储库
在应用程序中使用唯一的 Spring Data 模块使事情变得简单,因为定义范围内的所有存储库接口都绑定到 Spring Data 模块。 有时,应用程序需要使用多个 Spring Data 模块。 在这种情况下,存储库定义必须区分持久性技术。 当它在类路径上检测到多个存储库工厂时, Spring Data 进入严格的存储库配置模式。 严格配置使用存储库或域类的详细信息来决定存储库定义的 Spring Data 模块绑定:
-
如果存储库定义扩展了特定于模块的存储库,则它是特定 Spring Data 模块的有效候选者。
-
如果域类使用特定于模块的类型 Comments 进行 Comments,则它是特定 Spring Data 模块的有效候选者。 Spring Data 模块接受第三方注释(例如 JPA)或提供自己的注释(例如用于 Spring Data MongoDB 和 Spring Data Elasticsearch)。
@Entity
@Document
以下示例显示了使用特定于模块的接口(在本例中为 JPA)的存储库:
interface MyRepository extends JpaRepository<User, Long> { }
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends JpaRepository<T, ID> { … }
interface UserRepository extends MyBaseRepository<User, Long> { … }
MyRepository
和 extend。
它们是 Spring Data JPA 模块的有效候选者。UserRepository
JpaRepository
以下示例显示了使用泛型接口的存储库:
interface AmbiguousRepository extends Repository<User, Long> { … }
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … }
interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }
AmbiguousRepository
和 extend only 和 在其类型层次结构中。
虽然在使用唯一的 Spring Data 模块时这很好,但多个模块无法区分这些存储库应该绑定到哪个特定的 Spring Data。AmbiguousUserRepository
Repository
CrudRepository
以下示例显示了使用带有注释的域类的存储库:
interface PersonRepository extends Repository<Person, Long> { … }
@Entity
class Person { … }
interface UserRepository extends Repository<User, Long> { … }
@Document
class User { … }
PersonRepository
references ,它用 JPA 注释进行注释,因此此存储库显然属于 Spring Data JPA。 references ,它使用 Spring Data MongoDB 的注释进行注释。Person
@Entity
UserRepository
User
@Document
以下错误示例显示了使用具有混合注释的域类的存储库:
interface JpaPersonRepository extends Repository<Person, Long> { … }
interface MongoDBPersonRepository extends Repository<Person, Long> { … }
@Entity
@Document
class Person { … }
此示例显示了使用 JPA 和 Spring Data MongoDB 注释的域类。
它定义了两个存储库,以及 .
一个用于 JPA,另一个用于 MongoDB。
Spring Data 无法再区分存储库,这会导致未定义的行为。JpaPersonRepository
MongoDBPersonRepository
存储库类型详细信息和区分域类注释用于严格的存储库配置,以识别特定 Spring Data 模块的存储库候选者。 可以在同一域类型上使用多个特定于持久化技术的注释,并支持跨多个持久化技术重用域类型。 但是, Spring Data 无法再确定要绑定存储库的唯一模块。
区分存储库的最后一种方法是确定存储库基础包的范围。 基本包定义扫描存储库接口定义的起点,这意味着将存储库定义位于相应的包中。 默认情况下,注解驱动的配置使用 configuration 类的 package。 基于 XML 的配置中的基本软件包是必需的。
以下示例显示了基础包的注释驱动配置:
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }
2.4. 定义查询方法
存储库代理有两种方法可以从方法名称派生特定于存储的查询:
-
通过直接从方法名称派生查询。
-
通过使用手动定义的查询。
可用选项取决于实际商店。 但是,必须有一个策略来决定创建什么实际的查询。 下一节将介绍可用选项。
2.4.1. 查询查找策略
存储库基础设施可以使用以下策略来解析查询。
使用 XML 配置,您可以通过属性在命名空间处配置策略。
对于 Java 配置,您可以使用 Comments 的属性。
某些策略可能不支持特定数据存储。query-lookup-strategy
queryLookupStrategy
Enable${store}Repositories
-
CREATE
尝试从查询方法名称构造特定于存储的查询。 一般的方法是从方法名称中删除一组给定的已知前缀,并解析方法的其余部分。 您可以在 “Query Creation” 中阅读有关查询构造的更多信息。 -
USE_DECLARED_QUERY
尝试查找已声明的查询,如果找不到,则引发异常。 查询可以通过某处的 Comments 定义,也可以通过其他方式声明。 请参阅特定商店的文档,以查找该商店的可用选项。 如果存储库基础结构在引导时未找到该方法的声明查询,则它将失败。 -
CREATE_IF_NOT_FOUND
(默认)合并 和 。 它首先查找已声明的查询,如果未找到已声明的查询,则创建基于自定义方法名称的查询。 这是默认的查找策略,因此,如果您未显式配置任何内容,则使用该策略。 它允许通过方法名称快速定义查询,但也允许通过根据需要引入声明的查询来自定义调整这些查询。CREATE
USE_DECLARED_QUERY
2.4.2. 查询创建
Spring Data 存储库基础结构中内置的查询生成器机制对于构建对存储库实体的约束查询非常有用。
以下示例显示如何创建多个查询:
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
解析查询方法名称分为主题和谓词。
第一部分 (, ) 定义查询的主题,第二部分构成谓词。
引入子句 (subject) 可以包含进一步的表达式。
介于 和 之间(或其他引入关键字)和之间的任何文本都被视为描述性文本,除非使用结果限制关键字之一,例如 a 在要创建的查询上设置不同的标志,或使用 Top
/First
限制查询结果。find…By
exists…By
find
By
Distinct
附录包含查询方法主题关键字和查询方法谓词关键字的完整列表,包括排序和字母大小写修饰符。
但是,第一个 ID 充当分隔符,以指示实际条件谓词的开始。
在非常基本的层面上,您可以定义实体属性的条件,并将它们与 和 连接起来。By
And
Or
解析方法的实际结果取决于您为其创建查询的持久性存储。 但是,有一些一般事项需要注意:
-
表达式通常是属性遍历与可以连接的运算符组合。 您可以将属性表达式与 和 组合在一起。 您还可以获得对运算符(如 、 、 、 )和属性表达式的支持。 支持的运算符可能因数据存储而异,因此请参阅参考文档的相应部分。
AND
OR
Between
LessThan
GreaterThan
Like
-
方法解析器支持为单个属性(例如 )或支持忽略大小写的类型的所有属性(通常是 instances — 例如 )设置标志。 是否支持忽略大小写可能因商店而异,因此请参阅参考文档中的相关部分,了解特定于商店的查询方法。
IgnoreCase
findByLastnameIgnoreCase(…)
String
findByLastnameAndFirstnameAllIgnoreCase(…)
-
您可以通过将子句附加到引用属性的查询方法并提供排序方向 ( 或 ) 来应用静态排序。 要创建支持动态排序的查询方法,请参阅“特殊参数处理”。
OrderBy
Asc
Desc
2.4.3. 属性表达式
属性表达式只能引用托管实体的直接属性,如前面的示例所示。 在创建查询时,您已确保 parsed 属性是托管域类的属性。 但是,您也可以通过遍历嵌套属性来定义约束。 请考虑以下方法签名:
List<Person> findByAddressZipCode(ZipCode zipCode);
假设 a 具有 和 .
在这种情况下,该方法将创建属性 traversal。
解析算法首先将整个部分 () 解释为属性,并检查域类中是否有具有该名称(未大写)的属性。
如果算法成功,它将使用该属性。
如果不是,算法将右侧驼峰式部分的源拆分为 head 和 tail,并尝试找到相应的属性 — 在我们的示例中为 和 。
如果算法找到具有该 head 的属性,它会获取 tail 并继续从那里构建树,以刚才描述的方式将 tail 向上拆分。
如果第一个分割不匹配,则算法将分割点向左移动 (, ) 并继续。Person
Address
ZipCode
x.address.zipCode
AddressZipCode
AddressZip
Code
Address
ZipCode
尽管这应该适用于大多数情况,但算法可能会选择错误的属性。
假设该类也有一个属性。
该算法在第一轮拆分中已经匹配,选择了错误的属性,然后失败(因为 的类型可能没有属性)。Person
addressZip
addressZip
code
要解决这种歧义,您可以在方法名称中使用来手动定义遍历点。
所以我们的方法名称将如下所示:_
List<Person> findByAddress_ZipCode(ZipCode zipCode);
由于我们将下划线字符视为保留字符,因此强烈建议遵循标准的 Java 命名约定(即,不要在属性名称中使用下划线,而是使用驼峰式大小写)。
2.4.4. 特殊参数处理
要处理查询中的参数,请定义方法参数,如前面的示例中所示。
除此之外,基础架构还可以识别某些特定类型(如 和 ),以动态地将分页和排序应用于您的查询。
以下示例演示了这些功能:Pageable
Sort
Pageable
Slice
Sort
Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);
API 接受并期望将非值传递给方法。
如果您不想应用任何排序或分页,请使用 和 。Sort Pageable null Sort.unsorted() Pageable.unpaged() |
第一种方法允许您将实例传递给 query 方法,以动态地将分页添加到静态定义的查询中。
A 知道可用的元素和页面的总数。
它通过触发 count 查询来计算总数。
由于这可能很昂贵(取决于使用的存储),因此您可以改为返回 .
A 只知道 next 是否可用,这在遍历较大的结果集时可能就足够了。org.springframework.data.domain.Pageable
Page
Slice
Slice
Slice
排序选项也通过实例进行处理。
如果只需要排序,请向方法中添加参数。
如您所见,返回 a 也是可能的。
在这种情况下,不会创建构建实际实例所需的其他元数据(这反过来意味着不会发出本来必要的其他 count 查询)。
相反,它将查询限制为仅查找给定的实体范围。Pageable
org.springframework.data.domain.Sort
List
Page
要了解整个查询获得多少页,您必须触发额外的 count 查询。 默认情况下,此查询派生自您实际触发的查询。 |
分页和排序
您可以使用属性名称定义简单的排序表达式。 您可以连接表达式以将多个条件收集到一个表达式中。
Sort sort = Sort.by("firstname").ascending()
.and(Sort.by("lastname").descending());
要以更类型安全的方法来定义排序表达式,请从要为其定义排序表达式的类型开始,并使用方法引用来定义要排序的属性。
TypedSort<Person> person = Sort.sort(Person.class);
Sort sort = person.by(Person::getFirstname).ascending()
.and(person.by(Person::getLastname).descending());
TypedSort.by(…) 通过(通常)使用 CGlib 来使用运行时代理,这在使用 Graal VM Native 等工具时可能会干扰本机映像编译。 |
如果你的 store 实现支持 Querydsl,你也可以使用生成的元模型类型来定义排序表达式:
QSort sort = QSort.by(QPerson.firstname.asc())
.and(QSort.by(QPerson.lastname.desc()));
2.4.5. 限制查询结果
您可以使用 or 关键字来限制查询方法的结果,这些关键字可以互换使用。
您可以将可选数值附加到 或 以指定要返回的最大结果大小。
如果省略该数字,则假定结果大小为 1。
以下示例显示如何限制查询大小:first
top
top
first
Top
First
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
限制表达式还支持支持不同查询的数据存储的关键字。
此外,对于将结果集限制为一个实例的查询,支持使用关键字将结果包装到 中。Distinct
Optional
如果将分页或切片应用于限制查询分页(以及可用页数的计算),则会在有限结果中应用该分页或切片。
通过使用参数来限制结果与动态排序相结合,可以表示“K”最小元素和“K”最大元素的查询方法。Sort |
2.4.6. 返回集合或可迭代对象的存储库方法
返回多个结果的查询方法可以使用标准 Java 、 和 。
除此之外,我们还支持返回 Spring Data 的 、 的自定义扩展 以及 Vavr 提供的集合类型。
请参阅附录,其中说明了所有可能的查询方法返回类型。Iterable
List
Set
Streamable
Iterable
使用 Streamable 作为查询方法返回类型
您可以用作集合类型的替代项或任何集合类型。
它提供了访问非 parallel (missing from ) 的便捷方法,并能够直接和覆盖元素并将 连接到其他元素:Streamable
Iterable
Stream
Iterable
….filter(…)
….map(…)
Streamable
interface PersonRepository extends Repository<Person, Long> {
Streamable<Person> findByFirstnameContaining(String firstname);
Streamable<Person> findByLastnameContaining(String lastname);
}
Streamable<Person> result = repository.findByFirstnameContaining("av")
.and(repository.findByLastnameContaining("ea"));
返回自定义 Streamable 包装器类型
为集合提供专用包装器类型是一种常用的模式,用于为返回多个元素的查询结果提供 API。 通常,通过调用返回类似集合类型的存储库方法并手动创建包装类型的实例来使用这些类型。 你可以避免这个额外的步骤,因为 Spring Data 允许你使用这些包装器类型作为查询方法返回类型,如果它们满足以下条件:
-
该类型实现 .
Streamable
-
该类型公开一个构造函数或一个名为 OR 的静态工厂方法,该方法作为参数。
of(…)
valueOf(…)
Streamable
下面的清单显示了一个示例:
class Product { (1)
MonetaryAmount getPrice() { … }
}
@RequiredArgsConstructor(staticName = "of")
class Products implements Streamable<Product> { (2)
private final Streamable<Product> streamable;
public MonetaryAmount getTotal() { (3)
return streamable.stream()
.map(Priced::getPrice)
.reduce(Money.of(0), MonetaryAmount::add);
}
@Override
public Iterator<Product> iterator() { (4)
return streamable.iterator();
}
}
interface ProductRepository implements Repository<Product, Long> {
Products findAllByDescriptionContaining(String text); (5)
}
1 | 一个公开 API 以访问产品价格的实体。Product |
2 | 可以使用 (使用 Lombok 注释创建的工厂方法) 构造的 a 的包装器类型。
采用 will 的标准构造函数也可以。Streamable<Product> Products.of(…) Streamable<Product> |
3 | 包装器类型公开一个附加 API,用于计算 .Streamable<Product> |
4 | 实现接口并委托给实际结果。Streamable |
5 | 该包装类型可以直接用作查询方法返回类型。
您无需在存储库客户端的查询后返回并手动包装它。Products Streamable<Product> |
支持 Vavr 集合
Vavr 是一个包含 Java 函数式编程概念的库。 它附带了一组自定义的集合类型,您可以将其用作查询方法返回类型,如下表所示:
Vavr 集合类型 | 使用的 Vavr 实现类型 | 有效的 Java 源类型 |
---|---|---|
|
|
|
|
|
|
|
|
|
您可以使用第一列(或其子类型)中的类型作为查询方法返回类型,并获取第二列中用作实现类型的类型,具体取决于实际查询结果的 Java 类型(第三列)。
或者,你可以声明(Vavr 等价物),然后我们从实际的返回值中派生出实现类。
也就是说,a 变成 Vavr 或 ,a 变成 Vavr ,依此类推。Traversable
Iterable
java.util.List
List
Seq
java.util.Set
LinkedHashSet
Set
2.4.7. 存储库方法的 null 处理
从 Spring Data 2.0 开始,返回单个聚合实例的存储库 CRUD 方法使用 Java 8 来指示可能缺少值。
除此之外, Spring Data 支持在查询方法上返回以下包装器类型:Optional
-
com.google.common.base.Optional
-
scala.Option
-
io.vavr.control.Option
或者,查询方法可以选择根本不使用包装器类型。
然后,通过返回 来指示缺少查询结果。
返回集合、集合替代项、包装器和流的存储库方法保证永远不会返回,而是返回相应的空表示形式。
有关详细信息,请参阅“[repository-query-return-types]”。null
null
可为 Null 性注释
您可以使用 Spring Framework 的可为 null 性注释来表达存储库方法的可为 null 性约束。
它们提供了一种工具友好的方法,并在运行时提供选择加入检查,如下所示:null
-
@NonNullApi
:在包级别用于声明参数和返回值的默认行为分别是既不接受也不生成值。null
-
@NonNull
:用于不得为该参数或返回值的参数或返回值(在适用的情况下,参数和返回值不需要)。null
@NonNullApi
-
@Nullable
:用于可以是 .null
Spring 注解使用 JSR 305 注解(一种休眠但广泛使用的 JSR)进行元注解。
JSR 305 元注释允许工具供应商(例如 IDEA、Eclipse 和 Kotlin)以通用方式提供空安全支持,而不必对 Spring 注释进行硬编码支持。
要为查询方法启用可空性约束的运行时检查,您需要使用 Spring 的 in 在包级别激活非可空性,如以下示例所示:@NonNullApi
package-info.java
package-info.java
@org.springframework.lang.NonNullApi
package com.acme;
一旦非 null 默认值到位,存储库查询方法调用将在运行时验证是否为 null 性约束。
如果查询结果违反定义的约束,则会引发异常。
当方法返回但被声明为不可为空(在存储库所在的包上定义的注释的默认值)时,会发生这种情况。
如果要再次选择加入可为 null 的结果,请有选择地对单个方法使用。
使用本节开头提到的结果包装器类型将继续按预期工作:空结果将转换为表示 absence 的值。null
@Nullable
以下示例显示了刚才描述的许多技术:
package com.acme; (1)
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 | 存储库驻留在我们为其定义了非 null 行为的包(或子包)中。 |
2 | 当查询未生成结果时引发 an。
当传递给 method 的 为 时引发 an 。EmptyResultDataAccessException IllegalArgumentException emailAddress null |
3 | 当查询未生成结果时返回。
也接受作为 的值。null null emailAddress |
4 | 当查询未生成结果时返回。
当传递给 method 的 为 时引发 an 。Optional.empty() IllegalArgumentException emailAddress null |
基于 Kotlin 的存储库中的 Null 性
Kotlin 将可为 null 性约束的定义融入到语言中。
Kotlin 代码编译为字节码,字节码不通过方法签名来表示可为 null 性约束,而是通过编译的元数据来表示。
确保在项目中包含 JAR,以便能够内省 Kotlin 的可为 null 性约束。
Spring Data 存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:kotlin-reflect
interface UserRepository : Repository<User, String> {
fun findByUsername(username: String): User (1)
fun findByFirstname(firstname: String?): User? (2)
}
1 | 该方法将参数和结果定义为不可为 null(Kotlin 默认值)。
Kotlin 编译器拒绝传递给该方法的方法调用。
如果查询产生空结果,则抛出 an。null EmptyResultDataAccessException |
2 | 此方法接受参数,如果查询未生成结果,则返回。null firstname null |
2.4.8. 流式查询结果
您可以使用 Java 8 作为返回类型以增量方式处理查询方法的结果。
不是将查询结果包装在 中,而是使用特定于数据存储的方法来执行流式处理,如以下示例所示:Stream<T>
Stream
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);
A 可能会包装特定于底层数据存储的资源,因此必须在使用后关闭。
您可以使用该方法或使用 Java 7 块手动关闭 ,如以下示例所示:Stream Stream close() try-with-resources |
Stream<T>
try-with-resources
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
并非所有 Spring Data 模块当前都支持作为返回类型。Stream<T> |
2.4.9. 异步查询结果
您可以使用 Spring 的异步方法运行功能异步运行存储库查询。
这意味着该方法在调用时立即返回,而实际查询发生在已提交给 Spring 的任务中。
异步查询与反应式查询不同,不应混合使用。
有关反应式支持的更多详细信息,请参阅特定于 store 的文档。
以下示例显示了许多异步查询:TaskExecutor
@Async
Future<User> findByFirstname(String firstname); (1)
@Async
CompletableFuture<User> findOneByFirstname(String firstname); (2)
@Async
ListenableFuture<User> findOneByLastname(String lastname); (3)
1 | 用作返回类型。java.util.concurrent.Future |
2 | 使用 Java 8 作为返回类型。java.util.concurrent.CompletableFuture |
3 | 使用 a 作为返回类型。org.springframework.util.concurrent.ListenableFuture |
2.5. 创建 repository 实例
本节介绍如何为定义的存储库接口创建实例和 Bean 定义。一种方法是使用支持存储库机制的每个 Spring Data 模块附带的 Spring 名称空间,尽管我们通常建议使用 Java 配置。
2.5.1. XML配置
每个 Spring Data 模块都包含一个元素,该元素允许您定义 Spring 为您扫描的基本包,如以下示例所示:repositories
<?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 扫描及其所有子包以查找扩展接口或其子接口之一。
对于找到的每个接口,基础设施会注册特定于持久性技术的 Persistence 技术,以创建处理查询方法调用的相应代理。
每个 bean 都注册在从接口名称派生的 bean 名称下,因此接口 将在 下注册。
嵌套存储库接口的 Bean 名称以其封闭类型名称为前缀。
该属性允许使用通配符,以便您可以定义扫描的包的模式。com.acme.repositories
Repository
FactoryBean
UserRepository
userRepository
base-package
使用过滤器
默认情况下,基础架构会选取扩展位于已配置基础包下的特定于持久性技术的子接口的每个接口,并为其创建一个 bean 实例。
但是,您可能希望对哪些接口创建了 bean 实例进行更精细的控制。
为此,请在元素中使用 and 元素。
语义与 Spring 的 context 名称空间中的元素完全相同。
有关详细信息,请参阅这些元素的 Spring 参考文档。Repository
<include-filter />
<exclude-filter />
<repositories />
例如,要从实例化中排除某些接口作为存储库 bean,可以使用以下配置:
<repositories base-package="com.acme.repositories">
<context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>
前面的示例排除了所有以 结尾的接口,使其无法实例化。SomeRepository
2.5.2. Java 配置
您还可以通过在 Java 配置类上使用特定于 store 的注释来触发存储库基础架构。有关 Spring 容器的基于 Java 的配置的介绍,请参阅 Spring 参考文档中的 JavaConfig。@Enable${store}Repositories
启用 Spring Data 存储库的示例配置类似于以下内容:
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
前面的示例使用特定于 JPA 的注释,您将根据实际使用的 store 模块更改该注释。这同样适用于 bean 的定义。请参阅涵盖特定于 store 的配置的部分。EntityManagerFactory |
2.5.3. 独立使用
您还可以在 Spring 容器之外使用存储库基础结构,例如,在 CDI 环境中。你的 Classpath 中仍然需要一些 Spring 库,但是,通常,你也可以通过编程方式设置存储库。提供存储库支持的 Spring Data 模块附带了您可以使用的特定于持久性技术的模块,如下所示:RepositoryFactory
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
2.6. Spring 数据存储库的自定义实现
Spring Data 提供了各种选项来创建查询方法,只需很少的编码。 但是,当这些选项不符合您的需求时,您也可以为存储库方法提供自己的自定义实现。 本节介绍如何执行此操作。
2.6.1. 自定义单个存储库
要使用自定义功能丰富存储库,您必须首先定义自定义功能的片段接口和实现,如下所示:
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
类名中与 fragment 接口对应的最重要的部分是后缀。Impl |
实现本身不依赖于 Spring Data,可以是常规的 Spring bean。
因此,您可以使用标准依赖关系注入行为来注入对其他 bean(例如 a )、参与 aspects 等的引用。JdbcTemplate
然后,您可以让您的存储库接口扩展 fragment 接口,如下所示:
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
使用存储库接口扩展片段接口将 CRUD 和自定义功能相结合,并使其可供客户端使用。
Spring Data 存储库是通过使用形成存储库组合的片段来实现的。 片段是基本存储库、功能方面(如 QueryDsl)和自定义接口及其实现。 每次向存储库界面添加接口时,都会通过添加片段来增强合成。 基本存储库和存储库方面实现由每个 Spring Data 模块提供。
以下示例显示了自定义接口及其实现:
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
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}
存储库可能由多个自定义实现组成,这些实现按其声明顺序导入。 自定义实施的优先级高于基本实施和存储库方面。 通过此排序,您可以覆盖基本存储库和方面方法,并在两个片段提供相同的方法签名时解决歧义。 存储库片段不限于在单个存储库界面中使用。 多个存储库可以使用片段界面,从而允许您在不同的存储库中重复使用自定义设置。
以下示例显示了存储库片段及其实现:
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
}
}
以下示例显示了使用上述存储库片段的存储库:
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}
interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
配置
如果您使用命名空间配置,则存储库基础架构会尝试通过扫描在其中找到存储库的软件包下的类来自动检测自定义实现片段。
这些类需要遵循将 namespace 元素的属性附加到 fragment 接口名称的命名约定。
此后缀默认为 。
以下示例显示了使用默认后缀的存储库和为后缀设置自定义值的存储库:repository-impl-postfix
Impl
<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />
前面示例中的第一个配置尝试查找一个称为 to act a custom repository implementation的类。
第二个示例尝试查找 。com.acme.repository.CustomizedUserRepositoryImpl
com.acme.repository.CustomizedUserRepositoryMyPostfix
歧义的解决
如果在不同的包中找到具有匹配类名的多个实现,则 Spring Data 将使用 Bean 名称来确定要使用的 Bean。
鉴于前面显示的以下两个自定义实现,使用第一个实现。
它的 bean 名称是 ,它与片段接口 () 加上后缀 的 bean 名称匹配。CustomizedUserRepository
customizedUserRepositoryImpl
CustomizedUserRepository
Impl
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
}
如果使用 对接口进行注释,则 bean 名称加将与 中为存储库实现定义的名称匹配,并使用它而不是第一个名称。UserRepository
@Component("specialCustom")
Impl
com.acme.impl.two
手动布线
如果您的自定义实现仅使用基于 Comments 的配置和自动装配,则前面显示的方法效果很好,因为它被视为任何其他 Spring bean。 如果您的实现片段 Bean 需要特殊连接,则可以声明 Bean 并根据上一节中描述的约定对其进行命名。 然后,基础结构按名称引用手动定义的 bean 定义,而不是自己创建一个定义。 以下示例显示了如何手动连接自定义实现:
<repositories base-package="com.acme.repository" />
<beans:bean id="userRepositoryImpl" class="…">
<!-- further configuration -->
</beans:bean>
2.6.2. 自定义 Base 仓库
当您想要自定义基本存储库行为以使所有存储库都受到影响时,上一节中描述的方法需要自定义每个存储库接口。 要改为更改所有存储库的行为,您可以创建一个实现来扩展特定于持久性技术的存储库基类。 然后,此类充当存储库代理的自定义基类,如以下示例所示:
class MyRepositoryImpl<T, ID>
extends SimpleJpaRepository<T, ID> {
private final EntityManager entityManager;
MyRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
// Keep the EntityManager around to used from the newly introduced methods.
this.entityManager = entityManager;
}
@Transactional
public <S extends T> S save(S entity) {
// implementation goes here
}
}
该类需要具有特定于 store 的存储库工厂实现使用的 super class 的构造函数。
如果存储库基类具有多个构造函数,请覆盖采用 an 加上特定于商店的基础设施对象(例如 an 或 a template 类)的构造函数。EntityInformation EntityManager |
最后一步是使 Spring Data 基础结构知道自定义的存储库基类。
在 Java 配置中,可以使用 Comments 的属性来执行此操作,如以下示例所示:repositoryBaseClass
@Enable${store}Repositories
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
XML 命名空间中提供了相应的属性,如以下示例所示:
<repositories base-package="com.acme.repository"
base-class="….MyRepositoryImpl" />
2.7. 从 Aggregate Roots 发布事件
由存储库管理的实体是聚合根。
在域驱动设计应用程序中,这些聚合根通常会发布域事件。
Spring Data 提供了一个名为 Comments 的注释,您可以在聚合根的方法上使用该注释,以使该发布尽可能简单,如以下示例所示:@DomainEvents
class AnAggregateRoot {
@DomainEvents (1)
Collection<Object> domainEvents() {
// … return events you want to get published here
}
@AfterDomainEventPublication (2)
void callbackMethod() {
// … potentially clean up domain events list
}
}
1 | 使用的方法可以返回单个事件实例或事件集合。
它不能接受任何参数。@DomainEvents |
2 | 发布所有事件后,我们有一个带有 .
您可以使用它来清理要发布的事件列表(以及其他用途)。@AfterDomainEventPublication |
每次调用以下 Spring Data 存储库方法之一时,都会调用这些方法:
-
save(…)
,saveAll(…)
-
delete(…)
, , ,deleteAll(…)
deleteAllInBatch(…)
deleteInBatch(…)
请注意,这些方法将聚合根实例作为参数。
这就是 this 明显缺失的原因,因为 implementations 可能会选择发出删除实例的 query,因此我们从一开始就无法访问聚合实例。deleteById(…)
2.8. Spring 数据扩展
本节记录了一组 Spring Data 扩展,这些扩展支持在各种上下文中使用 Spring Data。 目前,大多数集成都针对 Spring MVC。
2.8.1. querydsl 扩展
Querydsl 是一个框架,支持通过其 Fluent API 构建静态类型的类似 SQL 的查询。
几个 Spring Data 模块通过 ,提供与 Querydsl 的集成,如下例所示:QuerydslPredicateExecutor
public interface QuerydslPredicateExecutor<T> {
Optional<T> findById(Predicate predicate); (1)
Iterable<T> findAll(Predicate predicate); (2)
long count(Predicate predicate); (3)
boolean exists(Predicate predicate); (4)
// … more functionality omitted.
}
1 | 查找并返回与 .Predicate |
2 | 查找并返回与 .Predicate |
3 | 返回与 .Predicate |
4 | 返回与 exists.Predicate |
要使用 Querydsl 支持,请在存储库界面上扩展,如下例所示:QuerydslPredicateExecutor
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}
前面的示例允许您使用 Querydsl 实例编写类型安全的查询,如下例所示:Predicate
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);
2.8.2. Web 支持
支持存储库编程模型的 Spring Data 模块附带了各种 Web 支持。
与 Web 相关的组件要求 Spring MVC JAR 位于 Classpath 上。
其中一些甚至提供与 Spring HATEOAS 的集成。
通常,通过使用 JavaConfig 配置类中的 Comments 来启用集成支持,如下例所示:@EnableSpringDataWebSupport
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
注释注册了一些组件。
我们将在本节后面讨论这些内容。
它还在 Classpath 上检测 Spring HATEOAS,并为其注册集成组件(如果存在)。@EnableSpringDataWebSupport
或者,如果使用 XML 配置,请将 或 注册为 Spring bean,如下例所示 (for ):SpringDataWebConfiguration
HateoasAwareSpringDataWebConfiguration
SpringDataWebConfiguration
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
<!-- If you use Spring HATEOAS, register this one *instead* of the former -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
基本 Web 支持
上一节中所示的配置注册了一些基本组件:
-
A 使用
DomainClassConverter
类让 Spring MVC 从请求参数或路径变量中解析存储库管理的域类的实例。 -
HandlerMethodArgumentResolver
实现,让 Spring MVC 解析请求参数中的实例。Pageable
Sort
-
Jackson Modules 来解序/序列化像 and 这样的类型,或者存储特定的类型,具体取决于所使用的 Spring Data Module 。
Point
Distance
使用类DomainClassConverter
该类允许你直接在 Spring MVC 控制器方法签名中使用域类型,这样你就不需要通过存储库手动查找实例,如下例所示:DomainClassConverter
@Controller
@RequestMapping("/users")
class UserController {
@RequestMapping("/{id}")
String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}
该方法直接接收实例,无需进一步查找。
通过让 Spring MVC 首先将 path 变量转换为域类的类型,并最终通过调用为域类型注册的存储库实例来访问该实例,可以解析该实例。User
id
findById(…)
目前,必须实施存储库才有资格被发现以进行转换。CrudRepository |
用于 Pageable 和 Sort 的 HandlerMethodArgumentResolvers
上一节中所示的配置代码段还注册了 a 以及 .
注册启用 和 作为有效的控制器方法参数,如下例所示:PageableHandlerMethodArgumentResolver
SortHandlerMethodArgumentResolver
Pageable
Sort
@Controller
@RequestMapping("/users")
class UserController {
private final UserRepository repository;
UserController(UserRepository repository) {
this.repository = repository;
}
@RequestMapping
String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}
前面的方法签名会导致 Spring MVC 尝试使用以下默认配置从请求参数派生实例:Pageable
|
页面。0 索引,默认为 0。 |
|
要检索的页面的大小。默认值为 20。 |
|
应按格式排序的属性。默认排序方向为区分大小写的升序。如果要切换方向或区分大小写,请使用多个参数,例如 . |
要自定义此行为,请分别注册实现接口或接口的 Bean。
其方法被调用,允许您更改设置,如下例所示:PageableHandlerMethodArgumentResolverCustomizer
SortHandlerMethodArgumentResolverCustomizer
customize()
@Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
return s -> s.setPropertyDelimiter("<-->");
}
如果设置 existing 的属性不足以达到您的目的,请扩展 or 已启用 HATEOAS 的等效项,覆盖 or 方法,然后导入自定义配置文件,而不是使用注释。MethodArgumentResolver
SpringDataWebConfiguration
pageableResolver()
sortResolver()
@Enable
如果需要从请求中解析多个实例(例如,对于多个表),则可以使用 Spring 的 Comments 来区分一个实例。
然后,请求参数必须以 .
以下示例显示了生成的方法签名:Pageable
Sort
@Qualifier
${qualifier}_
String showUsers(Model model,
@Qualifier("thing1") Pageable first,
@Qualifier("thing2") Pageable second) { … }
您必须填充 、 等。thing1_page
thing2_page
传递给方法的默认值等效于 a ,但您可以通过对参数使用注释来自定义它。Pageable
PageRequest.of(0, 20)
@PageableDefault
Pageable
分页对象的超媒体支持
Spring HATEOAS 附带了一个表示模型类 (),它允许使用必要的元数据和链接来丰富实例的内容,以使客户端轻松导航页面。
a 到 a 的转换是通过 Spring HATEOAS 接口的实现完成的,该接口称为 .
下面的示例展示了如何使用 a 作为控制器方法参数:PagedResources
Page
Page
Page
PagedResources
ResourceAssembler
PagedResourcesAssembler
PagedResourcesAssembler
@Controller
class PersonController {
@Autowired PersonRepository repository;
@RequestMapping(value = "/persons", method = RequestMethod.GET)
HttpEntity<PagedResources<Person>> persons(Pageable pageable,
PagedResourcesAssembler assembler) {
Page<Person> persons = repository.findAll(pageable);
return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
}
}
启用配置,如前面的示例所示,允许将其用作控制器方法参数。
调用它有以下效果:PagedResourcesAssembler
toResources(…)
-
的内容将成为实例的内容。
Page
PagedResources
-
该对象附加了一个实例,并填充了来自 和 底层 的信息。
PagedResources
PageMetadata
Page
PageRequest
-
可能会 get 和附加链接,具体取决于页面的状态。 这些链接指向方法映射到的 URI。 添加到该方法的分页参数与 的设置匹配,以确保以后可以解析链接。
PagedResources
prev
next
PageableHandlerMethodArgumentResolver
假设数据库中有 30 个实例。
您现在可以触发请求 () 并查看类似于以下内容的输出:Person
GET http://localhost:8080/persons
{ "links" : [ { "rel" : "next",
"href" : "http://localhost:8080/persons?page=1&size=20" }
],
"content" : [
… // 20 Person instances rendered here
],
"pageMetadata" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
汇编器生成了正确的 URI,并选取了默认配置,以将参数解析为即将到来的请求。
这意味着,如果您更改该配置,链接将自动遵循更改。
默认情况下,汇编器指向调用它的控制器方法,但您可以通过传递要用作基础的自定义来构建分页链接来自定义该方法,这会重载该方法。Pageable
Link
PagedResourcesAssembler.toResource(…)
Spring Data Jackson 模块
核心模块和一些特定于 store 的模块附带了一组 Jackson 模块,用于 Spring Data 域使用的类型,如 和 。
这些模块在启用 Web 支持并可用后导入。org.springframework.data.geo.Distance
org.springframework.data.geo.Point
com.fasterxml.jackson.databind.ObjectMapper
在初始化期间,就像 一样,基础设施会拾取,以便声明的 s 可供 Jackson 使用。SpringDataJacksonModules
SpringDataJacksonConfiguration
com.fasterxml.jackson.databind.Module
ObjectMapper
以下域类型的数据绑定混合由通用基础设施注册。
org.springframework.data.geo.Distance org.springframework.data.geo.Point org.springframework.data.geo.Box org.springframework.data.geo.Circle org.springframework.data.geo.Polygon
单个模块可能会提供额外的 . |
Web 数据绑定支持
您可以使用 Spring Data 投影(在 [projections] 中描述)通过使用 JSONPath 表达式(需要 Jayway JsonPath)或 XPath 表达式(需要 XmlBeam)来绑定传入的请求有效负载,如下例所示:
@ProjectedPayload
public interface UserPayload {
@XBRead("//firstname")
@JsonPath("$..firstname")
String getFirstname();
@XBRead("/lastname")
@JsonPath({ "$.lastname", "$.user.lastname" })
String getLastname();
}
您可以将前面示例中所示的类型用作 Spring MVC 处理程序方法参数,或者对 .
前面的方法声明将尝试在给定文档中的任何位置查找。
XML 查找在传入文档的顶层执行。
它的 JSON 变体首先尝试顶级,但如果前者不返回值,则也会尝试嵌套在子文档中。
这样,可以很容易地缓解源文档结构的更改,而无需让 Client 端调用公开的方法(通常是基于类的有效负载绑定的缺点)。ParameterizedTypeReference
RestTemplate
firstname
lastname
lastname
lastname
user
支持嵌套投影,如 [projections] 中所述。
如果该方法返回复杂的非接口类型,则使用 Jackson 映射最终值。ObjectMapper
对于 Spring MVC,一旦激活,必要的转换器就会自动注册,并且所需的依赖项在 Classpath 上可用。
对于 用法,请注册 (JSON) 或手动。@EnableSpringDataWebSupport
RestTemplate
ProjectingJackson2HttpMessageConverter
XmlBeamHttpMessageConverter
有关更多信息,请参阅规范 Spring Data Examples 存储库中的 Web 投影示例。
Querydsl Web 支持
对于那些具有 QueryDSL 集成的存储,您可以从查询字符串中包含的属性派生查询。Request
请考虑以下查询字符串:
?firstname=Dave&lastname=Matthews
给定前面示例中的对象,您可以使用 ,将查询字符串解析为以下值,如下所示:User
QuerydslPredicateArgumentResolver
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
当在 Classpath 上找到 Querydsl 时,该功能会自动启用。@EnableSpringDataWebSupport |
将 添加到方法签名中会提供一个现成的 ,您可以使用 .@QuerydslPredicate
Predicate
QuerydslPredicateExecutor
类型信息通常从方法的返回类型中解析。
由于该信息不一定与域类型匹配,因此最好使用 .root QuerydslPredicate |
下面的示例演示如何在方法签名中使用:@QuerydslPredicate
@Controller
class UserController {
@Autowired UserRepository repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate, (1)
Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
1 | 将查询字符串参数解析为匹配项 .Predicate User |
默认绑定如下:
-
Object
在 simple properties 上为 。eq
-
Object
on collection 之类的属性如 .contains
-
Collection
在 simple properties 上为 。in
您可以通过 的属性 或通过使用 Java 8 并将方法添加到存储库接口来自定义这些绑定,如下所示:bindings
@QuerydslPredicate
default methods
QuerydslBinderCustomizer
interface UserRepository extends CrudRepository<User, String>,
QuerydslPredicateExecutor<User>, (1)
QuerydslBinderCustomizer<QUser> { (2)
@Override
default void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(user.username).first((path, value) -> path.contains(value)) (3)
bindings.bind(String.class)
.first((StringPath path, String value) -> path.containsIgnoreCase(value)); (4)
bindings.excluding(user.password); (5)
}
}
1 | QuerydslPredicateExecutor 提供对 的特定 Finder 方法的访问。Predicate |
2 | QuerydslBinderCustomizer 在存储库界面上定义的 Shortcuts 会自动选取 和 shortcuts 。@QuerydslPredicate(bindings=…) |
3 | 将属性的绑定定义为简单绑定。username contains |
4 | 将属性的默认绑定定义为不区分大小写的匹配项。String contains |
5 | 从解析中排除该属性。password Predicate |
您可以在应用存储库 或 中的特定绑定之前注册一个包含默认 Querydsl 绑定的 Bean。QuerydslBinderCustomizerDefaults @QuerydslPredicate |
2.8.3. 存储库填充器
如果你使用 Spring JDBC 模块,你可能熟悉对使用 SQL 脚本填充 a 的支持。
类似的抽象在存储库级别可用,尽管它不使用 SQL 作为数据定义语言,因为它必须与存储无关。
因此,填充器支持 XML(通过 Spring 的 OXM 抽象)和 JSON(通过 Jackson)来定义用于填充存储库的数据。DataSource
假设您有一个调用的文件,其中包含以下内容:data.json
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]
您可以使用 Spring Data Commons 中提供的存储库命名空间的 populator 元素来填充存储库。
要将上述数据填充到 您的 ,请声明类似于以下内容的 populator:PersonRepository
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd">
<repository:jackson2-populator locations="classpath:data.json" />
</beans>
前面的声明会导致文件被 Jackson 读取和反序列化。data.json
ObjectMapper
JSON 对象解组到的类型是通过检查 JSON 文档的属性来确定的。
基础结构最终会选择适当的存储库来处理已反序列化的对象。_class
要改用 XML 来定义存储库应填充的数据,您可以使用 element.
您可以将其配置为使用 Spring OXM 中可用的 XML 编组器选项之一。有关详细信息,请参阅 Spring 参考文档。
以下示例显示了如何使用 JAXB 取消编组存储库填充器:unmarshaller-populator
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd
http://www.springframework.org/schema/oxm
https://www.springframework.org/schema/oxm/spring-oxm.xsd">
<repository:unmarshaller-populator locations="classpath:data.json"
unmarshaller-ref="unmarshaller" />
<oxm:jaxb2-marshaller contextPath="com.acme" />
</beans>