1. 程式人生 > >QQA: Spring Data 如何查詢屬性是否在列表中

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); // ⑤
}
});
}
}

上面程式碼的注意點:

  1. 實現 JpaSpecificationExecutor
    來啟用 Specification,Repository 中會增加 findAll(Specification<T> spec) 等使用 Specification 的查詢方法。
  2. 這裡使用 Java 8 的 Lambda 表示式。等價於實現一個 Specification 例項。
  3. 返回結果為 List,可能會出現重複的結果,加上 distinct 來去重。
  4. cb.conjunction() 等價於 where 1=1
  5. 重要:呼叫 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

實際查詢時生成的 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。

參考資料