标题尽管有点长,但是确实意义明确的。可以先看一下两个简化后对象。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.