Il join FetchMode non fa differenza per le relazioni ManyToMany nei repository JPA di spring

Sto provando a fare questo:

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

genera n + 1 query quando faccio personRepository.findAll(); attraverso un repository JPA di Spring, proprio come se non avessi alcun set @Fetch . (Prima una query per ottenere tutte le persone e quindi una query per persona per recuperare i gruppi).

@Fetch(FetchMode.SUBSELECT) uso di @Fetch(FetchMode.SUBSELECT) funziona ! Genera solo 2 query. (Uno per tutte le persone e poi uno per i gruppi). Quindi l’ibernazione reagisce ad alcuni parametri di recupero, ma non il JOIN .

Ho anche provato a rimuovere la raccolta di EAGER senza fortuna.

 //... class Person { @ManyToMany() @Fetch(FetchMode.JOIN) private Set groups; //... } 

Sto usando Spring JPA, e questo è il codice per il mio repository:

 public interface PersonRepository extends JpaRepository { } 

JOIN non funziona attraverso Spring JPA, o sto facendo qualcosa di sbagliato?

Passando attraverso molti forum e blog da leggere per il tuo problema (credo che avresti potuto farlo prima di postarlo qui) anch’io penso che

@Fetch (FetchMode.JOIN) verrà ignorato se si utilizza l’interfaccia Query (ad esempio: session.createQuery ()) ma verrà utilizzata correttamente se si utilizza l’interfaccia Criteri.

Questo è praticamente un bug in Hibernate che non è mai stato risolto. È spiacevole perché molte applicazioni utilizzano l’interfaccia Query e non possono essere migrate facilmente nell’interfaccia Criteri.

Se si utilizza l’interfaccia Query, è sempre necessario aggiungere manualmente le istruzioni JOIN FETCH all’HQL.

Riferimenti Forum di spring di Hibernate Domanda simile 1

Inoltre non ho potuto ottenere @Fetch(FetchMode.JOIN) per funzionare quando si utilizza JPA (anche se funziona bene quando si utilizza l’hibernate Criteria api) e non sono riuscito a trovare esempi che spiegassero perché, ma posso pensare a qualche soluzione alternativa .

Il modo più semplice per caricare i gruppi con impazienza è usare JPQL:

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

Poiché stai usando spring-data-jpa, potresti anche caricare i Gruppi con avidità usando una Specification . (A partire da 1.4.x è ansible concatenare le specifiche che restituiscono null).

 final Specification fetchGroups = new Specification() { @Override public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { root.fetch("groups", JoinType.LEFT); query.distinct(true); return null; } }; 

Se nessuno di questi è un’opzione per te, probabilmente la tua scommessa migliore è usare @Fetch(FetchMode.SUBSELECT) .

Un’altra opzione è utilizzare @Fetch(FetchMode.SELECT) in combinazione con @BatchSize . @BatchSize aiuta a risolvere il problema delle query n + 1. Modificando la dimensione del batch è ansible ridurre la quantità di query eseguite a CEIL (n / batch_size) +1.

 @Entity @Table(name = "persons") public class Person { @Id String name; @ManyToMany(fetch = FetchType.EAGER) @BatchSize(size = 20) Set groups = new HashSet<>(); } @Entity @Table(name = "groups") public class Group { @Id String name; @ManyToMany(mappedBy = "groups", fetch = FetchType.LAZY) Set persons = new HashSet<>(); } public interface PersonRepository extends JpaRepository{} 

Questo mapping genera il seguente sql quando si esegue personRepository.findAll(); su un database contenente 10 persone e @BatchSize impostato su 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 ( ?, ?, ?, ?, ? ) 

Nota che @BatchSize funziona anche per le raccolte mappate con FetchType.LAZY .