Et quel type de stratégies alternatives utilisez-vous pour éviter les LazyLoadExceptions?
Je comprends que la session ouverte en vue a des problèmes avec:
Mais, si vous savez que votre application s'exécute sur une seule machine virtuelle, pourquoi ne pas soulager votre douleur en utilisant une stratégie de session ouverte dans la vue?
Parce que l'envoi de procurations éventuellement non initialisées, en particulier des collections, dans la couche de vue et le déclenchement du chargement en veille prolongée à partir de là peuvent être problématiques à la fois du point de vue des performances et de la compréhension.
Comprendre :
L'utilisation de l'OSIV "pollue" la couche de visualisation avec des préoccupations liées à la couche d'accès aux données.
La couche de vue n'est pas prête à gérer un HibernateException
qui peut se produire lors d'un chargement paresseux, mais la couche d'accès aux données l'est probablement.
Performances :
OSIV a tendance à tirer la bonne entité sous le tapis - vous avez tendance à ne pas remarquer que vos collections ou entités sont initialisées paresseusement (peut-être N + 1). Plus de confort, moins de contrôle.
Mise à jour: voir L'antipattern OpenSessionInView pour une discussion plus large sur ce sujet. L'auteur énumère trois points importants:
- chaque initialisation paresseuse vous obtiendra une requête signifiant que chaque entité aura besoin de N + 1 requêtes, où N est le nombre d'associations paresseuses. Si votre écran présente des données tabulaires, la lecture du journal d'Hibernate est un indice important que vous ne faites pas comme vous le devriez
- cela vainc complètement l'architecture en couches, car vous souillez vos ongles avec DB dans la couche de présentation. Ceci est un con conceptuel, donc je pourrais vivre avec mais il y a un corollaire
- last but not least, si une exception se produit lors de la récupération de la session, elle se produira lors de l'écriture de la page: vous ne pouvez pas présenter une page d'erreur propre à l'utilisateur et la seule chose que vous pouvez faire est d'écrire un message d'erreur dans le corps
Pour une description plus longue, vous pouvez lire mon Open Session In View Anti-Pattern article. Sinon, voici un résumé des raisons pour lesquelles vous ne devriez pas utiliser Open Session In View.
Open Session In View adopte une mauvaise approche pour récupérer les données. Au lieu de laisser la couche métier décider de la meilleure façon de récupérer toutes les associations nécessaires à la couche View, elle force le contexte de persistance à rester ouvert afin que la couche View puisse déclencher l'initialisation du proxy.
OpenSessionInViewFilter
appelle la méthode openSession
du SessionFactory
sous-jacent et obtient un nouveau Session
.Session
est lié au TransactionSynchronizationManager
.OpenSessionInViewFilter
appelle le doFilter
du javax.servlet.FilterChain
référence d'objet et la demande est traitée ultérieurementDispatcherServlet
est appelé et il achemine la requête HTTP vers le PostController
sous-jacent.PostController
appelle le PostService
pour obtenir une liste des entités Post
.PostService
ouvre une nouvelle transaction et le HibernateTransactionManager
réutilise le même Session
qui a été ouvert par le OpenSessionInViewFilter
.PostDAO
récupère la liste des entités Post
sans initialiser aucune association paresseuse.PostService
valide la transaction sous-jacente, mais Session
n'est pas fermé car il a été ouvert en externe.DispatcherServlet
commence à rendre l'interface utilisateur, qui, à son tour, parcourt les associations paresseuses et déclenche leur initialisation.OpenSessionInViewFilter
peut fermer le Session
, et la connexion à la base de données sous-jacente est également libérée.À première vue, cela pourrait ne pas sembler être une chose terrible à faire, mais, une fois que vous le voyez du point de vue de la base de données, une série de défauts commence à devenir plus évidente.
La couche de service ouvre et ferme une transaction de base de données, mais par la suite, aucune transaction explicite n'est en cours. Pour cette raison, chaque instruction supplémentaire émise à partir de la phase de rendu de l'interface utilisateur est exécutée en mode de validation automatique. La validation automatique exerce une pression sur le serveur de base de données car chaque instruction doit vider le journal des transactions sur le disque, provoquant ainsi beaucoup de trafic d'E/S du côté base de données. Une optimisation serait de marquer le Connection
comme étant en lecture seule, ce qui permettrait au serveur de base de données d'éviter d'écrire dans le journal des transactions.
Il n'y a plus de séparation des préoccupations car les instructions sont générées à la fois par la couche service et par le processus de rendu de l'interface utilisateur. Écrire des tests d'intégration qui affirmer le nombre d'instructions générées nécessite de passer par toutes les couches (web, service, DAO), tout en ayant l'application déployée sur un conteneur web. Même lors de l'utilisation d'une base de données en mémoire (par exemple HSQLDB) et d'un serveur Web léger (par exemple Jetty), ces tests d'intégration seront plus lents à exécuter que si les couches étaient séparées et que les tests d'intégration d'arrière-plan utilisaient la base de données, tandis que le les tests d'intégration frontaux se moquaient complètement de la couche de service.
La couche d'interface utilisateur est limitée aux associations de navigation qui peuvent, à leur tour, déclencher des problèmes de requête N + 1. Bien qu'Hibernate propose @BatchSize
pour récupérer les associations par lots, et FetchMode.SUBSELECT
pour faire face à ce scénario, les annotations affectent le plan de récupération par défaut, elles sont donc appliquées à chaque cas d'utilisation métier. Pour cette raison, une requête de couche d'accès aux données est beaucoup plus appropriée car elle peut être adaptée aux exigences actuelles de récupération des données du cas d'utilisation.
Enfin, la connexion à la base de données peut être maintenue tout au long de la phase de rendu de l'interface utilisateur (selon votre mode de libération de connexion), ce qui augmente le temps de location de la connexion et limite le débit global des transactions en raison de l'encombrement du pool de connexions à la base de données. Plus la connexion est maintenue, plus les autres demandes simultanées vont attendre pour obtenir une connexion du pool.
Donc, soit vous maintenez la connexion trop longtemps, soit vous acquérez/libérez plusieurs connexions pour une seule requête HTTP, ce qui met la pression sur le pool de connexions sous-jacent et limite l'évolutivité.
Malheureusement, Open Session in View est activé par défaut dans Spring Boot .
Assurez-vous donc que dans le application.properties
fichier de configuration, vous disposez de l'entrée suivante:
spring.jpa.open-in-view=false
Cela désactivera OSIV, afin que vous puissiez gérer le LazyInitializationException
de la bonne façon .
les transactions peuvent être validées dans la couche service - les transactions ne sont pas liées à OSIV. C'est le Session
qui reste ouvert, pas une transaction - en cours d'exécution.
si vos couches d'application sont réparties sur plusieurs machines, alors vous avez à peu près ne peut pas utiliser OSIV - vous devez initialiser tout ce dont vous avez besoin avant d'envoyer l'objet sur le câble.
OSIV est un moyen agréable et transparent (c'est-à-dire qu'aucun de votre code ne sait que cela se produit) pour utiliser les avantages de chargement paresseux en termes de performances
Je ne dirais pas qu'Open Session In View est considéré comme une mauvaise pratique; qu'est-ce qui vous donne cette impression?
Open-Session-In-View est une approche simple pour gérer les sessions avec Hibernate. Parce que c'est simple, c'est parfois simpliste. Si vous avez besoin d'un contrôle précis sur vos transactions, comme avoir plusieurs transactions dans une demande, Open-Session-In-View n'est pas toujours une bonne approche.
Comme d'autres l'ont souligné, il y a des compromis à faire avec OSIV - vous êtes beaucoup plus enclin au problème N + 1 parce que vous êtes moins susceptible de réaliser quelles transactions vous démarrez. Dans le même temps, cela signifie que vous n'avez pas besoin de modifier votre couche de service pour vous adapter aux modifications mineures de votre vue.
Si vous utilisez un conteneur d'inversion de contrôle (IoC) tel que Spring, vous voudrez peut-être lire sur portée du bean . Essentiellement, je dis à Spring de me donner un objet Hibernate Session
dont le cycle de vie s'étend sur toute la demande (c'est-à-dire qu'il est créé et détruit au début et à la fin de la demande HTTP). Je n'ai pas à me soucier des LazyLoadException
s ni de la fermeture de la session car le conteneur IoC gère cela pour moi.
Comme mentionné, vous devrez penser aux problèmes de performances de N + 1 SELECT. Vous pouvez toujours configurer votre entité Hibernate par la suite pour effectuer un chargement de jointure rapide dans les endroits où les performances sont un problème.
La solution de détermination de la portée du haricot n'est pas spécifique au printemps. Je sais que PicoContainer offre la même capacité et je suis sûr que d'autres conteneurs IoC matures offrent quelque chose de similaire.
D'après ma propre expérience, OSIV n'est pas si mal. Le seul arrangement que j'ai fait est d'utiliser deux transactions différentes: - la première, ouverte dans la "couche service", où j'ai la "logique métier" - la seconde ouverte juste avant le rendu de la vue
Je viens de faire un post sur certaines directives quant à l'utilisation de la session ouverte en vue sur mon blog. Vérifiez-le si vous êtes intéressé.
http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/
Cela n'aidera pas trop mais vous pouvez vérifier mon sujet ici: * Hibernate Cache1 OutOfMemory avec OpenSessionInView
J'ai des problèmes avec OutOfMemory en raison d'OpenSessionInView et de nombreuses entités chargées, car elles restent dans le cache Hibernate niveau 1 et ne sont pas récupérées (je charge beaucoup d'entités avec 500 éléments par page, mais toutes les entités restent dans le cache)
Je suis v. Rusty sur Hibernate .. mais je pense qu'il est possible d'avoir plusieurs transactions en une seule session Hibernate. Vos limites de transaction ne doivent donc pas être les mêmes que les événements de démarrage/arrêt de session.
OSIV, imo, est principalement utile parce que nous pouvons éviter d'écrire du code pour démarrer un "contexte de persistance" (session a.k.a.) à chaque fois que la demande doit effectuer un accès DB.
Dans votre couche de service, vous devrez probablement effectuer des appels à des méthodes qui ont des besoins de transaction différents, tels que "Obligatoire, Nouveau requis, etc." La seule chose dont ces méthodes ont besoin, c'est que quelqu'un (c'est-à-dire le filtre OSIV) ait démarré le contexte de persistance, de sorte que la seule chose dont ils doivent s'inquiéter est - "hé, donnez-moi la session d'hibernation pour ce fil .. J'ai besoin de faire quelques Trucs DB ".