Spring Data Jpa 阅读

Spring Data Jpa 阅读

1. 核心概念

Spring Data 的核心是 Repository 接口。它将实体类以及实体类的 ID 类型作为类型参数进行管理。此接口主要用作标记接口,用于捕获要使用的类型,并帮助您发现扩展此接口的接口。查看源码可以发现 Repository 什么内容也没有, 说明它就是作为一个标记接口使用的。

@Indexed
public interface Repository<T, ID> {
}

继承了 Repository 接口的 CrudRepository 接口为我们提供针对已实现 Repository 接口或其子接口的的实体类复杂的 CRUD 操作(即增删改查)。

@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
    // 根据实体对象更新到数据库,对应于 MySQL 中的 insert 、update
    <S extends T> S save(S var1);
    // 没有使用过,试试####
    <S extends T> Iterable<S> saveAll(Iterable<S> var1);
    // 根据实体类的主键查找实体对象
    Optional<T> findById(ID var1);
    // 根据实体类的主键判断实体对象是否存在与数据库中
    boolean existsById(ID var1);
    // 没有使用过,试试####
    Iterable<T> findAll();
    // 没有使用过,试试####
    Iterable<T> findAllById(Iterable<ID> var1);
    // 这个是真的好用,直接根据某一列就可以统计
    long count();
    // 根据实体类的主键删除某一条记录
    void deleteById(ID var1);
    // 根据实体对象删除相应的记录
    void delete(T var1);
    // 没有使用过,试试####
    void deleteAll(Iterable<? extends T> var1);
    // 没有使用过,试试####
    void deleteAll();
}

Spring Data 还提供特定于持久性技术的抽象,比如特定于 MySQL 数据库的 JpaRepository 和特定于 MongoDB 数据库的 MongoRepository 。这些接口继承了 CrudRepository 接口并且提供了提供通用的持久化技术如增删改查等和底层持久化技术。

// 没有使用过,试试####
PagingAndSortingRepository 接口继承了 CrudRepository 接口,并提供了额外的分页查找功能方法。

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
    Iterable<T> findAll(Sort sort);
    Page<T> findAll(Pageable pageable);
}

举一个例子,为了获得 size 为 20 的第 2 页 User ,我应该先获得一个 Bean,然后提起分页请求来查找第 2 页的 20 个 User:

PagingAndSortingRepository<User, Long> repository = // ... get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));

除了查询方法之外,还可以使用派生的 count 和 delete 。

public interface UserRepository extends CrudRepository<User, Long> {
    long countByLastname(String lastname);
    long deleteByLastname(String lastname);
    List<User> removeByLastname(String lastname);
}

2. 查询方法

标准的 CRUD 功能存储库通常可以查询底层的数据存储。使用 Spring Data 声明这些查询将分为 4 个步骤:
1. 声明一个继承 Repository 或其子接口的接口,并且填写接口应处理的实体类和 ID 类型。
2. 在接口中声明查询方法。

interface PersonRepository extends Repository<Person, Long> {
    List<Person> findByLastname(String lastname);
}
  1. 使用 JavaConfigXML configuration 为这些接口创建 Spring 代理对象。
    a. 使用 JavaConfiguraion 将创建类似于下面的类:
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EnableJpaRepositories
class Config {}

b. 使用 XML configuration 将声明类似于下面的 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
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
    <jpa:repositories base-package="com.acme.repositories"/>
</beans>

在这个例子中使用的是 Jpa 命名空间。我们如果要使用其他模型的 Repository 接口,就需要更改为相应模块的命名空间声明。另外,由于默认使用已注解的包,JavaConfig 变体将不需要显式配置包。为了扫描自定义的包,必须使用 @Enable${store}Repository 注解的 base-package 属性。
4. 注入 Repository 实例并使用它,例子如下:

class SomeClient {
    private final PersonRepository repository;
    SomeClient(PersonRepository repository) {
        this.repository = repository;
    }
    void doSomething() {
        List<Person> persons = repository.findByLastname("Matthews");
    }
}

3. 定义 Repository 接口

首先,定义一个实体类特定的 Repository 接口。这个接口必须继承 Repository 接口,并且填写相应的实体类和 ID 类型。如果想要使用 CRUD 方法,应该继承 CrudRepository 接口。

3.1 Repository 定义

通常,自定义的 Repository 接口应该继承 Repository ,CrudRepository 或 PagingAndSortingRepository 接口。但是,要是不想继承 Spring Data 接口,可以使用 @RepositoryDefinition 来注解自定义的 Repository 接口。继承 CrudRepository 接口将公开一整套操作实体的方法。如果想要有选择地公开方法,从 CrudRepository 接口中复制你想要公开的方法到实体类 Repository 中去。
以下示例显示如何有选择地公开 CRUD 方法(在本例中为findById和save):

@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> 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);
}

中间的 Repository 接口应该用 @NoRepositoryBean 注解。在运行时 Spring Data 不应创建实例的接口上必须添加该注解。

3.2 Repository 方法的 Null 处理

在 Spring Data 2.0,Repository 中返回单个聚合实例的 CRUD 方法将使用 Java 8 的 Optional 类型来表明可能空值。除了 Java 8 的 Optional 类型以外,Spring Data 支持查询方法返回以下包装类型:
- com.google.common.base.Optional
- scala.Option
- io.vavr.control.Option
- javaslang.control.Option (deprecated as Javaslang is deprecated)

另外,查询方法可以选择不使用任何的包装类型。如果查询结果没有数据意味着将返回一个 null 。如果返回类型是集合、包装器或流的 Repository 方法将不返回 null 值,而是返回相应的空表示。细节部分请看“Repository query return types”。

Null 注解

通过使用 Spring Framework’s nullability annotions 可以为 Repository 方法表示空的约束。它们提供了一个工具友好的方法和运行时的 null 检查,如下所示:
- @NonNullApi:在包级别上声明参数或返回值不接受或产生 null 值。
- @NonNull:声明参数或返回值不能为 null 。
- @Nullable:声明参数或返回值可以为 null 。

一旦设置默认是 non-null,调用 Repository 的查询方法将在运行时验证可空性约束。如果查询执行结果违反了定义的约束,则抛出异常。这将发生在声明了非 null 但方法返回 null 的情况下。如果想再次使用可 null 的结果,可在各个方法上有选择地使用 @Nullable 。使用在开头部分提到的结果包装器类型继续按预期工作:一个空的结果表示没有值。
以下例子展示了刚才描述的许多技术:

// Repository 所在包被定义为 non-null 行为
@org.springframework.lang.NonNullApi;
package com.acme;                                                       
import org.springframework.lang.Nullable;
public interface UserRepository extends Repository<User, Long> {
    // 当执行的查询未产生结果时,抛出 EmptyResultDataAccessException 。
    // 当传递给方法的 emailAddress 为 null 时,抛出 IllegalArgumentException 。
    User getByEmailAddress(EmailAddress emailAddress);                    
    // 当执行的查询未产生结果时返回 null 。
    // 同时接受 null 作为 emailAddress 的值。
    @Nullable
    User findByEmailAddress(@Nullable EmailAddress emailAdress);          
    // 当执行的查询未产生结果时返回 Optional.empty()。
    // 当传递给方法的 emailAddress 为 null 时,抛出 IllegalArgumentException 。
    Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); 
}

3.3 使用多 Spring 数据模块的存储库

在应用中使用单一的 Spring 数据模块使事务简单,因为定义范围内的所有 Repository 接口都要与 Spring 数据模块绑定。有时,应用要求使用多于一个的 Spring 数据模块。在这样的情况下,一个 Repository 定义必须能区分不同的持久性技术。当它在类路径上检测到多 Repository 工厂时,Spring 数据进入严格的 Repository 配置模式。严格的配置使用 Repository 或实体类上的详细信息确定 Repository定义的 Spring 数据模块的绑定:
1. 如果 Repository 定义继承特定模块的 Repository ,那么它是特定 Spring 数据模块有效的候选者。
2. 如果使用特定模块类型的注解对实体类进行注解,那么它是特定 Spring 数据模块有效的候选者。Spring 数据模块接受第三方注解(如 JPA 的 @Entity ),也提供了它们自己的注解(如使用于 Spring Data MongoDB 和 Spring Data Elastisearch 的 @Document )。

以下例子展示了一个使用特定模块接口 JPA 的 Repository:

public interface MyRepository extends JpaRepository<User, Long> { }
@NoRepositoryBean
public interface MyBaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
    …
}
public interface UserRepository extends MyBaseRepository<User, Long> {
    …
}
// 在类型层次结构中,MyRepository 和 UserRepository 继承了 JpaRepository,
// 它们都是 Spring Data JPA 模块的有效候选者。

以下例子展示了一个使用通用接口的 Repository:

public interface AmbiguousRepository extends Repository<User, Long> {
    …
}
@NoRepositoryBean
public interface MyBaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
    …
}
public interface AmbiguousUserRepository extends MyBaseRepository<User, Long> {
    …
}
// 在类型层次结构中,AmbiguousRepository 和 AmbiguousUserRepository 只继承了 Repository 和 CrudRepository 。
// 虽然在使用单一的 Spring Data 模块时是完全正常的,
// 但是多模块无法区分这些 Repository 应该绑定哪个特定的 Spring Data 。

以下例子展示了一个使用带注解的实体类的 Repository:

interface PersonRepository extends Repository<Person, Long> {
    …
}
@Entity
class Person {
    …
}
interface UserRepository extends Repository<User, Long> {
    …
}
@Document
class User {
    …
}
// PersonRepository 引用使用 JPA @Entiry 注解的 Person ,
// 因此该 Repository 属于 Spring Data JPA 。
// UserRepository 引用使用 Spring Data MongoDB @Document 注解的 User。

以下的错误例子展示了一个使用带混杂注解的实体类的 Repository:

interface JpaPersonRepository extends Repository<Person, Long> {
    …
}
interface MongoDBPersonRepository extends Repository<Person, Long> {
    …
}
@Entity
@Document
class Person {
    …
}
// Spring Data 不能区分 Repository,导致未定义错误。

Repository 类型详细信息和区分实体类的注解用于为特定 Spring Data 模块区分候选者的严格 Repository 配置。在同一个实体类上使用多个特定的持久性技术注解是可能的,并且允许跨多持久性技术重用实体类型。然而,Spring Data 不能确定单一的模块来绑定 Repository 。
区分 Repository 的最后一个方法是使用 Repository 基础包。基础包定义在扫描 Repository 接口定义的开始点,这意味着 Repository 定义放在相应的包中。默认情况下,注解驱动的配置使用配置类的包。基础包在 XML 配置中是强制的。
以下例子展示了基本包的注解驱动配置:

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

4. 定义查询方法

Repository 代理有两种方法可以从方法名称派生特定于存储的查询:
- 直接从方法名称派生查询
- 使用手动定义的查询

可用选项取决于实际存储。然而,有一个策略确定产生的实际查询是什么。接下来的部分描述这些可用选项。

4.1 Query Lookup 策略

Repository 基础结构可以使用以下策略解析查询。使用 XML 配置,可以在命名空间通过 query-lookup-strategy 属性配置策略。对于 JavaConfig 配置,可以使用 @Enable${store}Repository 注解的 queryLookupStrategy 属性。在特定数据存储中可能不支持某些策略。
- CREATE 将从查询方法名称中创建一个特定存储的查询。一般方法是从方法名称中删除一组已知的前缀,并解析方法的其余部分。
- USE_DECLARED_QUERY 尝试查找一个已声明的查询,如果没有查找到将抛出一个异常。查询可以通过注解定义,或通过其他方法声明。如果 Repository 基本配置在启动时间没有查找到一个该方法已声明的查找,USE_DECLARED_QUERY 将失败。
- CREATE_IF_NOT_FOUND(默认)组合了 CREATE 和 USE_DECLARED_QUERY 。首先,它查找一个已声明的查询,如果没有查找到,它将产生一个以方法名称为基础的查询。这是默认的查找策略。如果没有明确的其他配置,将使用此策略。此策略允许通过方法名称快速创建查询定义,也可以通过传递已声明查询自定义。

4.2 Query Creation 策略

内建在 Spring Data Repository 配置中的查询创建机制有助于创建约束在实体上的查询。此机制剥离来自该方法的前缀 find…By,read…By,query…By,count…By,和get…By并开始解析其余部分。此机制可以包含更多的表达式,例如 Distinct 表达式可以设置产生的查询结果是不重复的。然而,第一个出现的 By 充当实际条件开始的分隔符。在非常基本的查询中,可在实体属性上定义条件,并用 And 和 Or 将它们连接起来。以下例子展示了怎样产生一组查询:

public interface PersonRepository extends Repository<User, Long> {
    List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
    // 为查询启用不重复标志
    List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
    List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
    // 在单独的属性上忽略大小写
    List<Person> findByLastnameIgnoreCase(String lastname);
    // 在所有合适的属性上忽略大小写
    List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
    // 在查询上使用静态排序
    List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
    List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

解析方法的实际结果取决于你要对哪一个持久性存储产生查询。但是,一般要注意的如下:
- 表达式通常是属性遍历与可以连接的运算符相结合。可用 And 和 Or 连接属性表达式,也可用 Between ,LessThan ,GreaterThan 和 Like 运算符。支持的运算符因数据存储而异,所以应查阅相应参考文档。
- 可以支持为各个属性设置忽略大小写 IgnoreCase ,也支持为所有属性设置大小写 AllIgnoreCase。
- 可通过设置 OrderBy 来静态排序属性。

4.3 属性表达式

属性表达式只能引用被管理的实体中的直接属性。在查询产生时,确保解析的是实体类中的属性。但是,您也可以通过遍历嵌套属性来定义约束。下面有一个方法:

List<Person> findByAddressZipCode(ZipCode zipCode);

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

虽然解析算法在大多数情况下有效,但还是有可能选择错误的属性。假设 Person 类有一个属性 addressZip,解析算法将匹配第一个分割,选择错误的属性。为了解决歧义,可一在方法名称内用 _ 手动定义分割点。因为解析算法将 _ 作为保留字符,属性命名最好使用驼峰命名法。

4.4 处理特殊参数

解析算法可以识别特定的类型,如 Pageable 和 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);

将 org.springframework.data.domain.Pageable 实例传递给查询方法,动态地将分页添加到静态定义的查询中。Page 对象知道可用页面和元素的总数。此方法将通过基本配置触发计数查询来计算总数。由于 Page 构建元数据开销很大,可返回 Slice 来替代。Slice 实例只需知道下一个元素是否可用,这在遍历更大的结果集时已经足够了。
排序选项也通过 Pageable 实例处理。如果需要排序,在方法中添加 org.springframework.data.domain.Sort 参数,查询结果可以返回 List\<>。在这种情况下,不会创建构建实际 Page 实例所需的其他元数据(这反过来意味着不会发出必要的附加计数查询)。相反,它限制查询仅查找给定范围的实体。
要了解整个查询的页数,必须触发额外的计数查询。默认情况下,此查询是从实际触发的查询派生的。

4.5 限制查询结果

查询结果可以 first 或 top 关键字来限制。可选的数值可以附加到 top 或 first 后面,以指定要返回的最大结果大小。如果不指定数值,查询方法返回大小为 1 的结果。以下的例子展示了怎样限制查询大小:

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 关键字将结果包装。如果将分页或切片应用于限制查询(以及可用分页的数目计算)中,它将限制查询结果。
通过使用 Sort 参数将结果与动态排序结合使用,可以表达“K”最小元素以及“K”元素的查询方法。

4.6 流式查询结果

可以使用 Java 8 Stream \

@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

Stream 可以手动关闭,也可使用 Java 7 try-with-resources 代码块。
当前并非所有的 Spring Data 模块都支持 Stream\

4.7 异步查询结果

可以使用 Spring 的异步方法执行功能异步运行存储库查询。这意味着该方法在调用时立即返回,而实际的查询执行发生在已提交给 Spring TaskExecutor 的任务中。异步查询执行与响应式查询执行不同,不应混合使用。
以下例子展示了一组异步查询:

// 使用 java.util.concurrent.Future 作为返回类型
@Async
Future<User> findByFirstname(String firstname);
// 使用 Java 8 java.util.concurrent.CompletableFuture 作为返回类型
@Async
CompletableFuture<User> findOneByFirstname(String firstname);
// 使用 org.springframework.util.concurrent.ListenableFuture 作为返回类型
@Async
ListenableFuture<User> findOneByLastname(String lastname);

5. 创建 Repository 实例

在这个部分,为已定义的 Repository 接口创建实例和 Bean 定义。一个方法是使用每个 Spring Data 模块提供的支持存储库机制的 Spring 命名空间,但一般情况下推荐使用 Java 配置。

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
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    http://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 下注册。base-package 属性允许使用通配符,以便可以定义扫描包的模式。

使用过滤器

默认情况下,基础结构会选择扩展位于已配置的基础包下的特定于持久性技术的 Repository 子接口的每个接口,并为其创建一个 bean 实例。然而,想要更细粒度地控制哪一个接口产生 bean 实例,需要在 repositories元素中使用 include-filter 和 exclude-filter 元素。更多请看Spring reference documentation for these elements
例如,要将某些接口从实例化中排除为bean,可以使用以下配置:

<repositories base-package="com.acme.repositories">
  <context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>

5.2 JavaConfig 配置

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值