65

QQA: Spring Data 如何查询属性是否在列表中

 5 years ago
source link: https://lotabout.me/2018/QQA-Spring-Data-query-attribute-in-collection/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

每篇文章有多个标签,选中多个标签,要求找出含有该标签的文章。同时如果选中标签为空,则返回所有文章。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 如下:

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。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK