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中查询枚举字段的两种主要方式:
标准JPA方法:
- ✅ 简洁直观,适合简单查询
- ✅ 通过方法名自动生成SQL
- ❌ 复杂查询能力有限
原生查询+SpEL:
- ✅ 支持复杂SQL和数据库特定功能
- ✅ 动态查询构建更灵活
- ⚠️ 需要手动编写SQL,维护成本略高
选择建议:
- 简单枚举查询优先使用标准方法
- 需要数据库特定功能或复杂逻辑时使用原生查询
所有示例代码可在GitHub仓库获取。