FetchMode join makes no difference for ManyToMany relations in spring JPA repositories

后端 未结 2 1460
渐次进展
渐次进展 2020-12-03 03:54

I\'m trying to do this:

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


        
相关标签:
2条回答
  • 2020-12-03 04:17

    Going through many forums and blogs to read for your problem (I guess you might have done that before posting it here) I too think that

    @Fetch(FetchMode.JOIN) will be ignored if you use the Query interface (e.g.: session.createQuery()) but it will be properly used if you use the Criteria interface.

    This is practically a bug in Hibernate which was never resolved. It is unfortunate because a lot of applications use the Query interface and cannot be migrated easily to the Criteria interface.

    If you use the Query interface you always have to add JOIN FETCH statements into the HQL manually.

    References Hibernate Forum Spring Forum Similar Question 1

    0 讨论(0)
  • 2020-12-03 04:32

    I also couldn't get @Fetch(FetchMode.JOIN) to work when using JPA (although it works fine when using the hibernate Criteria api) and I also couldn't find any examples explaining why, but I can think of a few workarounds.

    The most straightforward way to load the Groups eagerly, is to use JPQL:

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

    As you are using spring-data-jpa, you could also load the Groups eagerly by using a Specification. (As of 1.4.x you can chain specs that return 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;
        }
    };
    

    If neither of these is an option for you, your best bet is probably to use @Fetch(FetchMode.SUBSELECT).

    Another option is to use @Fetch(FetchMode.SELECT) in combination with @BatchSize. @BatchSize helps to solve the problem of the n+1 queries. By tweaking the batch size you can reduce the amount of queries executed to 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>{}
    

    This mapping results in the following sql when you run personRepository.findAll(); on a database containing 10 persons and @BatchSize set to 5.

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

    Note that @BatchSize also works for collections mapped with FetchType.LAZY.

    0 讨论(0)
提交回复
热议问题