QQA: Spring Data 如何查詢屬性是否在列表中
每篇文章有多個標籤,選中多個標籤,要求找出含有該標籤的文章。同時如果選中標籤為空,則返回所有文章。Spring Data/JPA 如何實現?
可以使用 Spring Data Specification 動態建立查詢語句,最終結果如下:
public interface PostRepository extends JpaRepository<Post, Long>, JpaSpecificationExecutor<Post> { // ① default List<Post> query(List<Tag> tags) { return findAll((root, cq, cb) -> { // ② cq.distinct(true); // ③ if (tags == null || tags.isEmpty()) { return cb.conjunction(); // ④ } else { return root.join("tags").in(tags); // ⑤ } }); } }
上面程式碼的注意點:
-
實現
JpaSpecificationExecutor
來啟用 Specification,Repository 中會增加findAll(Specification<T> spec)
等使用 Specification 的查詢方法。 -
這裡使用 Java 8 的 Lambda 表示式。等價於實現一個
Specification
例項。 -
返回結果為
List
,可能會出現重複的結果,加上distinct
來去重。 -
cb.conjunction()
等價於where 1=1
。 -
重要:呼叫
join
來定位多對多的(或其它的關聯)屬性。
文章類,其中一篇文章可以有多個標籤Tag
:
@Entity public class Post { @Id @GeneratedValue private long id; private String name; @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH}) private Set<Tag> tags = new HashSet<>(); // ... }
標籤類,一個標籤可以被賦給多篇文章:
@Entity public class Tag { @Id @GeneratedValue private int id; private String name; private String createdBy; @ManyToMany(mappedBy = "tags") private Set<Post> posts = new HashSet<>(); // ... }
實際查詢時生成的 SQL 如下:
select distinct post0_.id as id1_0_, post0_.name as name2_0_ from post post0_ inner join post_tags tags1_ on post0_.id=tags1_.posts_id inner join tag tag2_ on tags1_.tags_id=tag2_.id where tag2_.id in ( ? , ? )
為什麼用 Specification
上面介紹的 Spring Specification 看起來比較複雜,其實如果只需要查詢一個屬性,可以直接定義 Spring Data 的 Query Method:
public interface PostRepository extends JpaRepository<Post, Long> { List<Post> findDistinctByTagsIn(List<Tag> tags); // ① default List<Post> query(List<Tag> tags) { // ② if (tags == null || tags.isEmpty()) { return findAll(); } else { return findDistinctByTagsIn(tags); } } }
當然,① 處的方法不能處理tags
為空時返回所有文章的需求,所以需要 ② 處的方法進行包裝。
那麼 Specification 還有什麼用呢?考慮多個查詢條件的組合,例如文章有多名作者,要根據作者和標籤共同查詢,則需要像這樣實現:
public interface PostRepository extends JpaRepository<Post, Long> { List<Post> findDistinctByTagsIn(List<Tag> tags); List<Post> findDistinctByAuthorsIn(List<Author> authors); List<Post> findDistinctByAuthorsInAndTagsIn(List<Author> authors, List<Tag> tags); default List<Post> query(List<Author> authors, List<Tag> tags) { if ((authors == null || authors.isEmpty()) && (tags == null || tags.isEmpty())) { return findAll(); } else if (authors == null || authors.isEmpty()) { return findDistinctByTagsIn(tags); } else if (tags == null || tags.isEmpty()) { return findDistinctByAuthorsIn(authors); } else { return findDistinctByTagsIn(tags); } } }
這種實現方式需要增加指數級的方法數量,因此更合適用 Specification 動態生成 Query。
- ofollow,noindex">Spring Data repository with empty IN clause 跟本文探討的問題相同,有更細緻的分析。不過它處理的屬性沒有關聯關係。
- https://www.jianshu.com/p/659e9715d01d Spring Data Specification 的一些使用示例。
- https://docs.oracle.com/javaee/6/tutorial/doc/gjivm.html Criteria API,說明挺詳細,然而還是看不太懂。