1. 概述

在使用Spring Data JPA构建持久层时,我们经常需要处理包含枚举字段的实体。这些枚举字段表示一组固定的常量,例如订单状态、用户角色或文章发布阶段。

基于枚举字段查询实体是常见需求,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. 仓库层

定义好数据模型后,创建继承JpaRepository的仓库接口:

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

后续章节将在此接口中添加查询方法,探索不同方式查询枚举字段。

3. 标准JPA查询方法

Spring Data JPA允许在仓库接口中通过方法名定义派生查询。这种方式对简单查询特别有效。

3.1. 按单个枚举值查询

通过方法名定义查询单个ArticleStage值的方法:

List<Article> findByStage(ArticleStage stage);

Spring Data JPA会根据方法名自动生成SQL查询。

还可以结合其他字段创建更精确的查询,例如按标题和阶段查询:

Article findByTitleAndStage(String title, ArticleStage stage);

使用Instancio生成测试数据验证查询:

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会生成使用IN子句的SQL查询。

测试验证方法行为:

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还支持原生SQL查询。原生查询在执行复杂SQL或调用数据库特定函数时特别有用。

结合SpEL(Spring表达式语言)和@Query注解,可以构建基于方法参数的动态查询。

4.1. 按单个枚举值查询

使用原生查询查询单个枚举值:

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

关键点: ✅ 设置nativeQuery = true启用原生SQL查询 ✅ 使用SpEL表达式:#{#stage?.name()}引用枚举值 ✅ ?操作符优雅处理null输入

测试验证:

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. 按多个枚举值查询

查询多个枚举值的原生查询:

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

核心技巧: ✅ 使用IN子句匹配多个枚举值 ✅ SpEL表达式#stages.![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中查询枚举字段的两种主要方式:

  1. 标准JPA方法

    • ✅ 简洁直观,适合简单查询
    • ✅ 通过方法名自动生成SQL
    • ❌ 复杂查询能力有限
  2. 原生查询+SpEL

    • ✅ 支持复杂SQL和数据库特定功能
    • ✅ 动态查询构建更灵活
    • ⚠️ 需要手动编写SQL,维护成本略高

选择建议

  • 简单枚举查询优先使用标准方法
  • 需要数据库特定功能或复杂逻辑时使用原生查询

所有示例代码可在GitHub仓库获取。


原始标题:Using Enum in Spring Data JPA Queries | Baeldung