JPA根据某个对象的集合属性是否包含某个特定对象查询

标题尽管有点长,但是确实意义明确的。可以先看一下两个简化后对象。Video 和 Star,它们的关系是一部Video有多个Star,这里的Star不是github上的star,而是actor or actress。

@Data
@Document
public class Video{
    @Id
    protected String id;
    @DBRef
    private Set<Star> stars = new LinkedHashSet<>();
}

@Data
@Document
public class Star{
    @Id
    protected String id;
    @Indexed
    private String name;
}

现在有个查询需求,根据Star的id去查询他(她)的所有参演Video.目前我们的思路是:
(1)根据id查询出某个Star star;
(2)然后查询所有的Video,然后遍历的时候用stars.contains(star)晒选;
(3)筛选的结果就是该Star所参演的Video.

public List<Video> findAllByStarId(String starId) {
        Star star = starService.findById(starId);
        List<Video> all = repo.findAll();
        if (CollectionUtils.isEmpty(all)) {
            return Collections.emptyList();
        }
        return all.stream()
                .filter(t -> t.getStars().contains(star))
               .sorted(Comparator.comparing(Video::getRelease).reversed())
               .collect(Collectors.toList());
}

就功能型,上面是满足的,但是你实际运行起来就会发现查询很慢,估计20-30s,显然不符合我们的预期。

所有上面的代码有一个致命的隐患,那就是List all = repo.findAll();这是一个超级大隐患,查询的时候一定不能一次查询所有的数据,然后很有可能程序 一下子就卡了,这个和数据的多少及对象大小有直接关系。在生产环境绝对不能这么写代码的。

那么JPA有没有为我们提供一个比较好的方式。 当然是有的,那就是把我们在后面filter(t -> t.getStars().contains(star))这段代码直接在查询的时候就搞定。

public interface VideoRepository extends MongoRepository<Video, BigInteger> {
    
    List<Video> findAllByStarsContains(Star star, Sort sort);
    
}

修改后的代码是下面这样的:

    public List<Video> findAllByStarId(String starId) {
        Star star = starService.findById(starId);
        List<Video> all = repo.findAllByStarsContains(star, Sort.by(Sort.Direction.DESC, "release"));
        return CollectionUtils.isEmpty(all) ? Collections.emptyList() : all;
}

后记:
(1) 如果我们建模的时候是双向建模,Star 也有多部Video到还可以一次可以查询出来,可能我们由于数据原因,没有建立这样的关联,再讲白一点,就是Star只是包含一个简单的属性(name),只是为了扩展,慢慢有了birth,height…属性独立出去了。
(2) JPA的强大之处就是帮省去了写实现的时间,你只要按照规范命名就可以了。至于规范,在IDEA中的提示真的很友好。

You worth it.

Posted in JPA