J'ai une relation entre trois objets de modèle dans mon projet (des extraits de modèle et de référentiel à la fin de l'article.
Quand j'appelle PlaceRepository.findById
il déclenche trois requêtes de sélection:
("sql")
SELECT * FROM place p where id = arg
SELECT * FROM user u where u.id = place.user.id
SELECT * FROM city c LEFT OUTER JOIN state s on c.woj_id = s.id where c.id = place.city.id
C'est un comportement plutôt inhabituel (pour moi). Pour autant que je sache après avoir lu la documentation Hibernate, il devrait toujours utiliser les requêtes JOIN. Il n'y a aucune différence dans les requêtes lorsqu'elles sont FetchType.LAZY
modifiées en FetchType.EAGER
dans la Place
classe (requête avec SELECT supplémentaire), de même pour la City
classe lorsqu'elle est FetchType.LAZY
modifiée en FetchType.EAGER
(requête avec JOIN).
Lorsque j'utilise la CityRepository.findById
suppression des incendies, deux sélections:
SELECT * FROM city c where id = arg
SELECT * FROM state s where id = city.state.id
Mon objectif est d'avoir un comportement sam dans toutes les situations (soit toujours JOIN ou SELECT, JOIN préféré cependant).
Définitions du modèle:
Endroit:
@Entity
@Table(name = "place")
public class Place extends Identified {
@Fetch(FetchMode.JOIN)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_user_author")
private User author;
@Fetch(FetchMode.JOIN)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "area_city_id")
private City city;
//getters and setters
}
Ville:
@Entity
@Table(name = "area_city")
public class City extends Identified {
@Fetch(FetchMode.JOIN)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "area_woj_id")
private State state;
//getters and setters
}
Dépôts:
LieuRepository
public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {
Place findById(int id);
}
UserRepository:
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findAll();
User findById(int id);
}
CityRepository:
public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom {
City findById(int id);
}
Réponses:
Je pense que Spring Data ignore le FetchMode. J'utilise toujours les annotations
@NamedEntityGraph
et@EntityGraph
lorsque je travaille avec Spring DataConsultez la documentation ici
la source
List<Place> findAll();
se termine par l'exceptionorg.springframework.data.mapping.PropertyReferenceException: No property find found for type Place!
. Cela fonctionne lorsque j'ajoute manuellement@Query("select p from Place p")
. Cela semble être une solution de contournement.@EntityGraph
est presque impossible dans les scénarios réels car il ne peut pas être spécifié quel type de produitFetch
nous voulons utiliser (JOIN
,SUBSELECT
,SELECT
,BATCH
). Ceci en combinaison avec l'@OneToMany
association et rend Hibernate Fetch toute la table en mémoire même si nous utilisons la requêteMaxResults
.Tout d'abord,
@Fetch(FetchMode.JOIN)
et@ManyToOne(fetch = FetchType.LAZY)
sont antagonistes, l'un ordonnant une récupération EAGER, tandis que l'autre suggérant une récupération LAZY.La récupération impatiente est rarement un bon choix et pour un comportement prévisible, il vaut mieux utiliser la
JOIN FETCH
directive query-time :la source
Spring-jpa crée la requête à l'aide du gestionnaire d'entités et Hibernate ignorera le mode de récupération si la requête a été créée par le gestionnaire d'entités.
Ce qui suit est le travail autour que j'ai utilisé:
Implémenter un référentiel personnalisé qui hérite de SimpleJpaRepository
Remplacez la méthode
getQuery(Specification<T> spec, Sort sort)
:Au milieu de la méthode, ajoutez
applyFetchMode(root);
pour appliquer le mode de récupération, pour que Hibernate crée la requête avec la jointure correcte.(Malheureusement, nous devons copier la méthode entière et les méthodes privées associées de la classe de base car il n'y avait pas d'autre point d'extension.)
Mettre en œuvre
applyFetchMode
:la source
"
FetchType.LAZY
" ne se déclenchera que pour la table primaire. Si dans votre code vous appelez une autre méthode qui a une dépendance de table parente, elle lancera une requête pour obtenir ces informations de table. (SÉLECTION MULTIPLE INCENDIE)"
FetchType.EAGER
" créera directement la jointure de toutes les tables, y compris les tables parent pertinentes. (UTILISATIONSJOIN
)Quand utiliser: Supposons que vous deviez obligatoirement utiliser les informations de la table parent dépendante, puis choisissez
FetchType.EAGER
. Si vous n'avez besoin d'informations que pour certains enregistrements, utilisezFetchType.LAZY
.N'oubliez pas, a
FetchType.LAZY
besoin d'une fabrique de session de base de données active à l'endroit de votre code où si vous choisissez de récupérer les informations de la table parent.Par exemple pour
LAZY
:Référence supplémentaire
la source
NamedEntityGraph
car je voulais un graphique d'objets non hydratés.Le mode de récupération ne fonctionnera que lors de la sélection de l'objet par id c'est-à-dire en utilisant
entityManager.find()
. Étant donné que Spring Data créera toujours une requête, la configuration du mode de récupération ne vous sera d'aucune utilité. Vous pouvez utiliser des requêtes dédiées avec des jointures d'extraction ou utiliser des graphiques d'entités.Lorsque vous souhaitez obtenir les meilleures performances, vous ne devez sélectionner que le sous-ensemble de données dont vous avez réellement besoin. Pour ce faire, il est généralement recommandé d'utiliser une approche DTO pour éviter de récupérer des données inutiles, mais cela entraîne généralement beaucoup de code passe-partout sujet aux erreurs, car vous devez définir une requête dédiée qui construit votre modèle DTO via un JPQL expression du constructeur.
Les projections de Spring Data peuvent aider ici, mais à un moment donné, vous aurez besoin d'une solution comme Blaze-Persistence Entity Views, ce qui rend cela assez facile et a beaucoup plus de fonctionnalités dans sa pochette qui vous seront utiles! Vous créez simplement une interface DTO par entité où les getters représentent le sous-ensemble de données dont vous avez besoin. Une solution à votre problème pourrait ressembler à ceci
Clause de non-responsabilité, je suis l'auteur de Blaze-Persistence, donc je suis peut-être partial.
la source
J'ai développé la réponse dream83619 pour qu'elle gère Hibernate imbriquée
@Fetch
annotations . J'ai utilisé une méthode récursive pour trouver des annotations dans des classes associées imbriquées.Vous devez donc implémenter un référentiel personnalisé et remplacer
getQuery(spec, domainClass, sort)
méthode de . Malheureusement, vous devez également copier toutes les méthodes privées référencées :(.Voici le code,
les méthodes privées copiées sont omises.EDIT: Ajout des méthodes privées restantes.
la source
http://jdpgrailsdev.github.io/blog/2014/09/09/spring_data_hibernate_join.html à
partir de ce lien:
si vous utilisez JPA sur Hibernate, il n'y a aucun moyen de définir le FetchMode utilisé par Hibernate sur JOIN Cependant, si vous utilisez JPA sur Hibernate, il n'y a aucun moyen de définir le FetchMode utilisé par Hibernate sur JOIN.
La bibliothèque Spring Data JPA fournit une API de spécifications de conception pilotées par domaine qui vous permet de contrôler le comportement de la requête générée.
la source
Selon Vlad Mihalcea (voir https://vladmihalcea.com/hibernate-facts-the-importance-of-fetch-strategy/ ):
Il semble que la requête JPQL puisse remplacer votre stratégie de récupération déclarée, vous devrez donc l'utiliser
join fetch
pour charger avec impatience une entité référencée ou simplement charger par identifiant avec EntityManager (ce qui obéira à votre stratégie de récupération mais pourrait ne pas être une solution pour votre cas d'utilisation ).la source