1、概览 {#1概览}
在使用 Spring Data JPA 构建持久层时,经常要处理带有枚举字段的实体。这些枚举字段代表一组固定的常量,例如订单的状态、用户的角色或业务的某个阶段。
本文将带你了解如何使用标准的 JPA 方法和原生查询来查询实体类中声明的枚举字段。
2、应用设置 {#2应用设置}
2.1、数据模型 {#21数据模型}
首先,定义数据模型,包括一个枚举字段。
我们示例中的中心实体是 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 层 {#22repository-层}
定义好数据模型后,就可以创建一个继承了 JpaRepository
的 Repository 接口,以便与数据库交互:
@Repository
public interface ArticleRepository extends JpaRepository<Article, UUID> {
}
接下来,我们要为该接口添加查询方法,探索通过枚举字段查询 Article
实体的不同方法。
3、标准的 JPA 查询方法 {#3标准的-jpa-查询方法}
Spring Data JPA 允许我们在 Repository 接口中使用方法名定义派生查询方法。这种方法非常适合简单的查询。
来看看如何使用它来查询实体类中的枚举字段。
3.1、根据单个枚举值查询 {#31根据单个枚举值查询}
在 ArticleRepository
接口中定义一个方法,根据单个 ArticleStage
枚举值查找文章:
List<Article> findByStage(ArticleStage stage);
Spring Data JPA 会根据方法名称生成相应的 SQL 查询。
还可以将 stage
参数与其他字段结合起来,创建更具体的查询。例如,根据 title
和 stage
查找文章:
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、根据多个枚举值查询 {#32根据多个枚举值查询}
还可以通过多个 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、原生查询 {#4原生查询}
除了上一节中介绍的标准 JPA 方法外,Spring Data JPA 还支持原生(Native) SQL 查询。原生查询有助于执行复杂的 SQL 查询,而且可以调用特定于数据库的函数。
此外,还可以使用 @Query
注解的 SpEL(Spring 表达式语言),根据方法参数构建动态查询。
接下来看看如何使用 SpEL 的原生查询功能,通过 ArticleStage
枚举值来检索实体类 Article
。
4.1、根据单个枚举值查询 {#41根据单个枚举值查询}
要使用原生查询根据单个枚举值检索文章记录,可以在 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、根据多个枚举值查询 {#42根据多个枚举值查询}
要使用原生查询按多个枚举值检索文章记录,可以在 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、总结 {#5总结}
本文介绍了如何使用 Spring Data JPA 查询实体类中的枚举字段,包括标准的 JPA 方法和使用 SpEL 的原生查询方法。
Ref:https://www.baeldung.com/spring-data-jpa-enums