web-dev-qa-db-fra.com

Pourquoi Hibernate Open Session in View est-il considéré comme une mauvaise pratique?

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:

  • Applications en couches exécutées dans différents jvm
  • Les transactions ne sont validées qu'à la fin, et vous aimeriez probablement les résultats avant.

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?

102
HeDinges

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:

  1. 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
  2. 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
  3. 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
45
Robert Munteanu

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.

enter image description here

  • OpenSessionInViewFilter appelle la méthode openSession du SessionFactory sous-jacent et obtient un nouveau Session.
  • Le Session est lié au TransactionSynchronizationManager .
  • Le OpenSessionInViewFilter appelle le doFilter du javax.servlet.FilterChain référence d'objet et la demande est traitée ultérieurement
  • DispatcherServlet est appelé et il achemine la requête HTTP vers le PostController sous-jacent.
  • Le PostController appelle le PostService pour obtenir une liste des entités Post.
  • Le 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é.

Spring Boot

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 .

39
Vlad Mihalcea
  • 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

24
Bozho

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.

13
Geoffrey Wiseman

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 LazyLoadExceptions 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.

5
0sumgain

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

4
Davide

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/

3
Chris Upton

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)

1
Sebastien Lorber

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 ".

1
rjk2008