在 Spring Data JPA 查询中使用枚举(Enum)

1、概览

在使用 Spring Data JPA 构建持久层时,经常要处理带有枚举字段的实体。这些枚举字段代表一组固定的常量,例如订单的状态、用户的角色或业务的某个阶段。

本文将带你了解如何使用标准的 JPA 方法和原生查询来查询实体类中声明的枚举字段。

2、应用设置

2.1、数据模型

首先,定义数据模型,包括一个枚举字段。

我们示例中的中心实体是 Article 类,它声明了一个枚举字段 ArticleStage,用于表示文章可能处于的不同阶段:

public enum ArticleStage {
    TODO, IN_PROGRESS, PUBLISHED;
}

ArticleStage 枚举包含三个可能的阶段,代表文章从最初创建到最终发布的生命周期。

接下来,创建声明了 ArticleStage 枚举字段的 Article 实体类:

@Entity
@Table(name = "articles")
public class Article {

    @Id
    private UUID id;

    private String title;

    private String author;

    @Enumerated(EnumType.STRING)
    private ArticleStage stage;

    // 构造函数/Getter/Setter 方法省略
}

我们将 Article 实体类映射到 articles 数据库表。此外,还使用 @Enumerated 注解指定 stage 字段应作为字符串在数据库中持久化。

2.2、Repository 层

定义好数据模型后,就可以创建一个继承了 JpaRepository 的 Repository 接口,以便与数据库交互:

@Repository
public interface ArticleRepository extends JpaRepository<Article, UUID> {
}

接下来,我们要为该接口添加查询方法,探索通过枚举字段查询 Article 实体的不同方法。

3、标准的 JPA 查询方法

Spring Data JPA 允许我们在 Repository 接口中使用方法名定义派生查询方法。这种方法非常适合简单的查询。

来看看如何使用它来查询实体类中的枚举字段。

3.1、根据单个枚举值查询

ArticleRepository 接口中定义一个方法,根据单个 ArticleStage 枚举值查找文章:

List<Article> findByStage(ArticleStage stage);

Spring Data JPA 会根据方法名称生成相应的 SQL 查询。

还可以将 stage 参数与其他字段结合起来,创建更具体的查询。例如,根据 titlestage 查找文章:

Article findByTitleAndStage(String title, ArticleStage stage);

使用 Instancio 生成 Article 测试数据并测试这些查询:

Article article = Instancio.create(Article.class);
articleRepository.save(article);

List<Article> retrievedArticles = articleRepository.findByStage(article.getStage());

assertThat(retrievedArticles).element(0).usingRecursiveComparison().isEqualTo(article);
Article article = Instancio.create(Article.class);
articleRepository.save(article);

Article retrievedArticle = articleRepository.findByTitleAndStage(article.getTitle(), article.getStage());

assertThat(retrievedArticle).usingRecursiveComparison().isEqualTo(article);

3.2、根据多个枚举值查询

还可以通过多个 ArticleStage 枚举值查找文章:

List<Article> findByStageIn(List<ArticleStage> stages);

Spring Data JPA 会生成一个 SQL 查询,使用 IN 子句查找 stage 与所提供的参数值中任何一个相匹配的文章。

测试:

List<Article> articles = Instancio.of(Article.class).stream().limit(100).toList();
articleRepository.saveAll(articles);

List<ArticleStage> stagesToQuery = List.of(ArticleStage.TODO, ArticleStage.IN_PROGRESS);
List<Article> retrievedArticles = articleRepository.findByStageIn(stagesToQuery);

assertThat(retrievedArticles)
  .isNotEmpty()
  .extracting(Article::getStage)
  .doesNotContain(ArticleStage.PUBLISHED)
  .hasSameElementsAs(stagesToQuery);

4、原生查询

除了上一节中介绍的标准 JPA 方法外,Spring Data JPA 还支持原生(Native) SQL 查询。原生查询有助于执行复杂的 SQL 查询,而且可以调用特定于数据库的函数。

此外,还可以使用 @Query 注解的 SpEL(Spring 表达式语言),根据方法参数构建动态查询。

接下来看看如何使用 SpEL 的原生查询功能,通过 ArticleStage 枚举值来检索实体类 Article

4.1、根据单个枚举值查询

要使用原生查询根据单个枚举值检索文章记录,可以在 ArticleRepository 接口中定义一个方法,并使用 @Query 注解对其进行注解:

@Query(nativeQuery = true, value = "SELECT * FROM articles WHERE stage = :#{#stage?.name()}")
List<Article> getByStage(@Param("stage") ArticleStage stage);

nativeQuery 属性设置为 true,表示使用的是原生 SQL 查询,而不是默认的 JPQL。

在查询中使用 SpEL 表达式 :#{#stage?.name()} 来引用传递给方法参数的枚举值。表达式中的 ? 操作符用于优雅地处理 nul 值。

测试如下:

Article article = Instancio.create(Article.class);
articleRepository.save(article);

List<Article> retrievedArticles = articleRepository.getByStage(article.getStage());

assertThat(retrievedArticles).element(0).usingRecursiveComparison().isEqualTo(article);

4.2、根据多个枚举值查询

要使用原生查询按多个枚举值检索文章记录,可以在 ArticleRepository 接口中定义另一种方法:

@Query(nativeQuery = true, value = "SELECT * FROM articles WHERE stage IN (:#{#stages.![name()]})")
List<Article> getByStageIn(@Param("stages") List<ArticleStage> stages);

如上,在 SQL 查询中使用 IN 子句来获取 stage 与所提供值中任何一个相匹配的文章。

SpEL 表达式 #stages.![name()] 将枚举值列表转换为代表其名称(name())的字符串列表。

测试如下:

List<Article> articles = Instancio.of(Article.class).stream().limit(100).toList();
articleRepository.saveAll(articles);

List<ArticleStage> stagesToQuery = List.of(ArticleStage.TODO, ArticleStage.IN_PROGRESS);
List<Article> retrievedArticles = articleRepository.findByStageIn(stagesToQuery);

assertThat(retrievedArticles)
  .isNotEmpty()
  .extracting(Article::getStage)
  .doesNotContain(ArticleStage.PUBLISHED)
  .hasSameElementsAs(stagesToQuery);

5、总结

本文介绍了如何使用 Spring Data JPA 查询实体类中的枚举字段,包括标准的 JPA 方法和使用 SpEL 的原生查询方法。


Ref:https://www.baeldung.com/spring-data-jpa-enums