12

我正在尝试这样做:

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

当我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;
//...
}

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

public interface PersonRepository extends JpaRepository<Person, Long> {
}

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

4

2 回答 2

22

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

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

这实际上是 Hibernate 中的一个错误,从未解决。不幸的是,很多应用程序都使用 Query 接口,不能轻易迁移到 Criteria 接口。

如果您使用查询接口,您总是必须手动将 JOIN FETCH 语句添加到 HQL 中。

参考 Hibernate 论坛 Spring 论坛 类似问题 1

于 2013-09-27T10:42:41.633 回答
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();
}

当您使用 spring-data-jpa 时,您还可以使用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;
    }
};

如果这些都不适合您,那么您最好的选择可能是使用@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>{}

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 (
        ?, ?, ?, ?, ?
    )

请注意,这@BatchSize也适用于使用FetchType.LAZY.

于 2013-09-27T23:03:11.713 回答