如何将多对多关联的JPQL join fetch查询改写为Spring Data方法名查询
结论
纯Spring Data方法名查询本身无法直接实现你的需求,因为方法名语法没有对应关联抓取(join fetch)的语义,但是可以通过方法名+@EntityGraph注解的组合方式实现,不需要手写JPQL。
原因说明
你之前写的方法名findByAuthorNameAndBooks_titleIn的作用仅仅是过滤符合条件的作者:查询「作者名匹配、且关联的书籍中存在标题在指定集合内」的作者,但是这个查询并不会将过滤后的书籍填充到Author的books集合中。查询返回的Author对象的books集合仍然是懒加载代理,你访问集合时Hibernate就会执行第二条SQL加载该作者的所有关联书籍,这和你之前查询时的过滤条件无关。
而你手写的JPQL之所以生效,是因为用了join fetch关键字,这条指令会告诉Hibernate:关联查询得到的符合条件的Book对象,直接填充到Author的books集合中,不需要后续再加载。
解决方案
在方法上添加@EntityGraph注解指定要抓取的关联属性即可,相当于在方法名查询中自动加入join fetch逻辑:
public interface AuthorRepository extends JpaRepository<Author, Long> { @EntityGraph(attributePaths = "books") Optional<Author> findByAuthorNameAndBooksTitleIn(String authorName, Set<String> titles); }
该方法生成的SQL和你手写的JPQL基本一致,会一次性查询出作者和符合条件的关联书籍,不会再触发第二条全量加载书籍的SQL,返回的Author对象的books集合中只会包含标题匹配的记录。
注意事项
默认该查询使用内连接,如果作者没有任何符合条件的书籍,方法会直接返回Optional.empty()。如果你需要即使没有匹配书籍也要返回作者对象(此时books集合为空),可以调整关联的连接类型为左连接,只需要给多对多注解添加属性:
@ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "author_book", joinColumns = @JoinColumn(name = "author_id"), inverseJoinColumns = @JoinColumn(name = "book_id")) private Set<Book> books = new HashSet<>();
同时修改EntityGraph的关联配置为左连接即可。
内容的提问来源于stack exchange,提问作者George Lvov

