FetchMode连接对Spring JPA存储库中的ManyToMany关系没有任何区别

Mat*_*ann 10 java spring hibernate jpa spring-data

我正在尝试这样做:

//...
class Person {
    @ManyToMany(fetch = FetchType.EAGER)
    @Fetch(FetchMode.JOIN)
    private Set<Group> groups;
//...
}
Run Code Online (Sandbox Code Playgroud)

当我personRepository.findAll();通过Spring JPA存储库时,它会生成n + 1个查询,就好像我没有任何@Fetch集合一样.(一个查询首先获取所有人,然后每个人查询一次以获取组).

但是,使用@Fetch(FetchMode.SUBSELECT) 作品!它只生成2个查询.(一个适用于所有人,一个适用于团体).所以hibernate会对一些 fetch参数作出反应,而不是JOIN.

我也试过EAGER没有运气去除取物.

//...
class Person {
    @ManyToMany()
    @Fetch(FetchMode.JOIN)
    private Set<Group> groups;
//...
}
Run Code Online (Sandbox Code Playgroud)

我正在使用Spring JPA,这是我的存储库的代码:

public interface PersonRepository extends JpaRepository<Person, Long> {
}
Run Code Online (Sandbox Code Playgroud)

JOIN是不是通过Spring JPA工作,还是我做错了什么?

Dar*_*rse 21

通过许多论坛和博客来阅读你的问题(我猜你可能在发布之前就已经这样做了)我也认为

如果使用Query接口(例如:session.createQuery()),将忽略@Fetch(FetchMode.JOIN),但如果使用Criteria接口,它将被正确使用.

这实际上是Hibernate中的一个从未解决的错误.很不幸,因为许多应用程序使用Query接口,无法轻松迁移到Criteria接口.

如果使用Query接口,则必须手动将JOIN FETCH语句添加到HQL中.

参考 Hibernate论坛 春季论坛 类似问题1


Pie*_*ter 9

@Fetch(FetchMode.JOIN)在使用JPA时也无法工作(虽然它在使用hibernate Criteria api时工作正常)但我也找不到任何解释原因的例子,但我可以想到一些解决方法.

急切加载组的最直接的方法是使用JPQL:

public interface PersonRepository extends JpaRepository<Person, String>{
  @Query(value = "select distinct p from Person p left join fetch p.groups")
  List<Person> getAllPersons();
}
Run Code Online (Sandbox Code Playgroud)

当您使用spring-data-jpa时,您也可以使用a来热切地加载组Specification.(从1.4.x开始,您可以链接返回null的规范).

final Specification<Person> fetchGroups = new Specification<Person>() {
    @Override
    public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        root.fetch("groups", JoinType.LEFT);
        query.distinct(true);
        return null;
    }
};
Run Code Online (Sandbox Code Playgroud)

如果这些都不是您的选择,那么您最好的选择就是使用@Fetch(FetchMode.SUBSELECT).

另一种选择是与... @Fetch(FetchMode.SELECT)结合使用@BatchSize.@BatchSize有助于解决n + 1个查询的问题.通过调整批量大小,您可以减少执行到CEIL(n/batch_size)+1的查询量.

@Entity
@Table(name = "persons")
public class Person {
  @Id
  String name;

  @ManyToMany(fetch = FetchType.EAGER)
  @BatchSize(size = 20)
  Set<Group> groups = new HashSet<>();
}

@Entity
@Table(name = "groups")
public class Group {
  @Id
  String name;

  @ManyToMany(mappedBy = "groups", fetch = FetchType.LAZY)
  Set<Person> persons = new HashSet<>();
}

public interface PersonRepository extends JpaRepository<Person, String>{}
Run Code Online (Sandbox Code Playgroud)

当您personRepository.findAll();在包含10个人并且@BatchSize设置为5 的数据库上运行时,此映射将生成以下sql .

Hibernate: 
select
    person0_.name as name1_ 
from
    persons person0_
Hibernate: 
select
    groups0_.persons_name as persons1_1_1_,
    groups0_.groups_name as groups2_1_,
    group1_.name as name0_0_ 
from
    persons_groups groups0_ 
inner join
    groups group1_ 
        on groups0_.groups_name=group1_.name 
where
    groups0_.persons_name in (
        ?, ?, ?, ?, ?
    )
Hibernate: 
select
    groups0_.persons_name as persons1_1_1_,
    groups0_.groups_name as groups2_1_,
    group1_.name as name0_0_ 
from
    persons_groups groups0_ 
inner join
    groups group1_ 
        on groups0_.groups_name=group1_.name 
where
    groups0_.persons_name in (
        ?, ?, ?, ?, ?
    )
Run Code Online (Sandbox Code Playgroud)

请注意,这@BatchSize也适用于映射的集合FetchType.LAZY.