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

108

Et quel genre 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 s'exécutant 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 vous soulager en utilisant une stratégie de session ouverte en vue?

HeDinges
la source
12
L'OSIV est-il considéré comme une mauvaise pratique? Par qui?
Johannes Brodwall
4
Et - quelles sont les bonnes alternatives?
David Rabinowitz
7
Cette paix de texte vient des développeurs de couture: Il y a plusieurs problèmes avec cette implémentation, le plus grave étant que nous ne pouvons jamais être sûrs qu'une transaction est réussie tant que nous ne l'avons pas validée, mais au moment où la transaction "open session in view" est validée, la vue est entièrement rendue et la réponse rendue a peut-être déjà été envoyée au client. Comment pouvons-nous informer l'utilisateur que sa transaction a échoué?
darpet
2
Voir ce billet de blog pour les avantages et les inconvénients et ma propre expérience à ce sujet - blog.jhades.org/open-session-in-view-pattern-pros-and-cons
Angular University

Réponses:

46

Parce que l'envoi de proxies éventuellement non initialisés, en particulier des collections, dans la couche de vue et le déclenchement du chargement de mise en veille prolongée à partir de là peut être gênant du point de vue des performances et de la compréhension.

Comprendre :

L'utilisation d'OSIV «pollue» la couche de vue 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 ce HibernateExceptionqui peut se produire lors d'un chargement différé, mais vraisemblablement la couche d'accès aux données l'est.

Performance :

OSIV a tendance à tirer le bon chargement des entités 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 commodité, moins de contrôle.


Mise à jour: voir L'anti-modèle OpenSessionInView pour une discussion plus large sur ce sujet. L'auteur énumère trois points importants:

  1. chaque initialisation paresseuse vous donnera une requête, ce qui signifie 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 va complètement à l'encontre de l'architecture en couches, car vous souillez vos ongles avec DB dans la couche de présentation. C'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
Robert Munteanu
la source
13
Ok, cela «pollue» la couche de vue avec une exception d'hibernation. Mais, en ce qui concerne les performances, je pense que le problème est assez similaire à celui d'accéder à une couche de service qui retournera votre dto. Si vous rencontrez un problème de performances, vous devez optimiser ce problème spécifique avec une requête plus intelligente ou un dto plus léger. Si vous devez développer trop de méthodes de service pour gérer les possibilités dont vous pourriez avoir besoin dans la vue, vous «polluez» également la couche de service. non?
HeDinges
1
Une différence est que cela retarde la fermeture de la session Hibernate. Vous allez attendre que le JSP soit rendu / écrit / etc, et cela garde les objets en mémoire plus longtemps. Cela peut être un problème, en particulier si vous devez écrire des données lors de la validation de session.
Robert Munteanu
8
Cela n'a pas de sens de dire que l'OSIV nuit aux performances. Quelles alternatives existe-t-il à l'exception de l'utilisation des DTO? Dans ce cas, vous aurez toujours des performances inférieures car les données utilisées par n'importe quelle vue devront être chargées même pour les vues qui n'en ont pas besoin.
Johannes Brodwall
11
Je pense que la pollution fonctionne dans l'autre sens. Si j'ai besoin de charger les données avec impatience, la couche logique (ou pire la couche d'accès aux données) a besoin de savoir de quelle manière un objet va être affiché. Changez la vue et vous finissez par charger des choses dont vous n'avez pas besoin ou des objets manquants dont vous avez besoin. Une exception Hibernate est un bogue et tout aussi empoisonnement que toute autre exception inattendue. Mais la performance est un problème. Les problèmes de performances et d'évolutivité vous obligeront à réfléchir et à travailler davantage dans votre couche d'accès aux données, et éventuellement à fermer la session plus tôt
Jens Schauder
1
@JensSchauder "Changez la vue et vous finirez par charger des choses dont vous n'avez pas besoin ou des objets manquants dont vous avez besoin". C'est exactement ça. Si vous modifiez la vue, il est bien préférable de charger des éléments dont vous n'avez pas besoin (car vous êtes plus susceptible d'être impatient de les récupérer) ou de déterminer les objets manquants comme vous obtiendriez l'exception de chargement paresseux, que de laisser la vue se charger cela paresseusement car cela entraînera le problème N + 1, et vous ne saurez même pas que cela se produit. Donc, l'OMI, c'est mieux la couche de service (et vous) savez ce qu'il est envoyé que la vue qui se charge paresseusement et vous ne savez rien à ce sujet.
Jeshurun
40

Pour une description plus longue, vous pouvez lire mon article Open Session In View Anti-Pattern . 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 des données. Au lieu de laisser la couche de gestion décider de la meilleure manière d'extraire toutes les associations nécessaires à la couche de vue, elle force le contexte de persistance à rester ouvert afin que la couche de vue puisse déclencher l'initialisation du proxy.

entrez la description de l'image ici

  • L' OpenSessionInViewFilterappelle la openSessionméthode du sous-jacent SessionFactoryet obtient un nouveau Session.
  • Le Sessionest lié au TransactionSynchronizationManager.
  • Les OpenSessionInViewFilterappels doFilterde la javax.servlet.FilterChainréférence d'objet et la demande est ensuite traitée
  • Le DispatcherServletest appelé et achemine la requête HTTP vers le sous-jacent PostController.
  • L' PostControllerappel le PostServicepour obtenir une liste d' Postentités.
  • Le PostServiceouvre une nouvelle transaction et HibernateTransactionManagerréutilise la même Sessionqui a été ouverte par le OpenSessionInViewFilter.
  • Le PostDAOrécupère la liste des Postentités sans initialiser aucune association paresseuse.
  • Le PostServicevalide la transaction sous-jacente, mais Sessionn'est pas fermé car il a été ouvert en externe.
  • Le DispatcherServletcommence le rendu de l'interface utilisateur, qui, à son tour, parcourt les associations différées et déclenche leur initialisation.
  • Le OpenSessionInViewFilterpeut fermer le Session, et la connexion à la base de données sous-jacente est également libérée.

À première vue, cela ne semble pas être une chose terrible à faire, mais, une fois que vous l'avez vue du point de vue d'une base de données, une série de failles commencent à devenir plus évidentes.

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. L'auto-validation met la pression sur le serveur de base de données car chaque instruction doit vider le journal des transactions sur le disque, ce qui entraîne un trafic d'E / S important côté base de données. Une optimisation serait de marquer le Connectioncomme é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 de service et par le processus de rendu de l'interface utilisateur. L'écriture de tests d'intégration affirmant le nombre d'instructions générées nécessite de passer par toutes les couches (web, service, DAO), tout en déployant l'application 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 back-end utilisaient la base de données, tandis que le les tests d'intégration frontale se moquaient complètement de la couche de service.

La couche d'interface utilisateur est limitée à la navigation dans les associations qui peuvent, à leur tour, déclencher des problèmes de requête N + 1. Bien qu'Hibernate propose @BatchSizede récupérer les associations par lots et FetchMode.SUBSELECTde 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 de récupération de données du cas d'utilisation actuel.

Enfin, la connexion à la base de données peut être maintenue tout au long de la phase de rendu de l'interface utilisateur (en fonction du mode de libération de votre connexion), ce qui augmente la durée du bail de connexion et limite le débit global des transactions en raison de la congestion du pool de connexions de la base de données. Plus la connexion est maintenue, plus d'autres requêtes simultanées vont attendre pour obtenir une connexion à partir du pool.

Donc, soit vous maintenez la connexion pendant trop longtemps, soit vous acquérez / libérez plusieurs connexions pour une seule requête HTTP, ce qui exerce une pression sur le pool de connexions sous-jacent et limite l'évolutivité.

Botte de printemps

Malheureusement, Open Session in View est activé par défaut dans Spring Boot .

Donc, assurez-vous que dans le application.propertiesfichier de configuration, vous avez l'entrée suivante:

spring.jpa.open-in-view=false

Cela désactivera OSIV, afin que vous puissiez gérer LazyInitializationExceptionla bonne manière .

Vlad Mihalcea
la source
3
Utiliser Open Session in View avec l'auto-commit est possible mais pas comme prévu par les développeurs Hibernate. Ainsi, même si Open Session in View a ses inconvénients, l'auto-commit n'en est pas un car vous pouvez simplement le désactiver et continuer à l'utiliser.
stefan.m
Vous parlez de ce qui se passe dans une transaction, et c'est vrai. Mais la phase de rendu de la couche Web se produit en dehors de Hibernate, vous obtenez donc le mode autocommit. Logique?
Vlad Mihalcea
Je pense que c'est une variante qui n'est pas la meilleure pour Open Session in View. La session et la transaction doivent rester ouvertes jusqu'à ce que la vue ait été rendue, alors il n'est pas nécessaire d'utiliser le mode autocommit.
stefan.m
2
La session reste ouverte. Mais la transaction ne fonctionne pas. L'étendue de la transaction sur l'ensemble du processus n'est pas non plus optimale car elle augmente sa longueur et les verrous sont maintenus plus longtemps que nécessaire. Imaginez ce qui se passe si la vue lève une RuntimeException. La transaction sera-t-elle annulée parce que le rendu de l'interface utilisateur a échoué?
Vlad Mihalcea
Merci beaucoup pour la réponse très détaillée! Je ne changerais que le guide à la fin, car les utilisateurs de Spring Boot n'utiliseront probablement pas jpa de cette manière.
Skeeve
24
  • les transactions peuvent être validées dans la couche service - les transactions ne sont pas liées à OSIV. C'est le Sessionqui reste ouvert, pas une transaction - en cours.

  • si vos couches d'application sont réparties sur plusieurs machines, vous ne pouvez pratiquement pas utiliser OSIV - vous devez initialiser tout ce dont vous avez besoin avant d'envoyer l'objet sur le fil.

  • OSIV est un moyen agréable et transparent (c'est-à-dire qu'aucun de votre code n'est conscient que cela se produit) pour utiliser les avantages en termes de performances du chargement paresseux

Bozho
la source
2
En ce qui concerne le premier point, ce n'est au moins pas vrai pour l' OSIV original du wiki JBoss, il gère également la démarcation des transactions autour de la demande.
Pascal Thivent
@PascalThivent Quelle partie vous a fait penser ainsi?
Sanghyun Lee
13

Je ne dirais pas qu'une session ouverte en vue est considérée comme une mauvaise pratique; qu'est-ce qui vous donne cette impression?

Open-Session-In-View est une approche simple de la gestion des 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 sujet au problème N + 1 parce que vous êtes moins susceptible de réaliser les transactions que vous lancez. En 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.

Geoffrey Wiseman
la source
5

Si vous utilisez un conteneur d'inversion de contrôle (IoC) tel que Spring, vous voudrez peut-être vous renseigner sur la portée du bean . Essentiellement, je dis à Spring de me donner un Sessionobjet Hibernate dont le cycle de vie couvre toute la requête (c'est-à-dire qu'il est créé et détruit au début et à la fin de la requête HTTP). Je n'ai pas à me soucier de LazyLoadExceptions ni de fermer la session puisque 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 hâtive dans les endroits où les performances sont un problème.

La solution de cadrage du bean n'est pas spécifique à Spring. 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.

0sommegain
la source
1
Avez-vous un pointeur vers une implémentation réelle des sessions Hibernate rendue disponible dans la vue via des beans à portée de requête?
Marvo
4

D'après ma propre expérience, OSIV n'est pas si mal. Le seul arrangement que j'ai fait utilise 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

Davide
la source
3

Je viens de publier un article sur les règles d'utilisation de la session ouverte dans mon blog. Vérifiez-le si vous êtes intéressé.

http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/

Chris Upton
la source
1
En règle générale, si vous fournissez une réponse, il vaut mieux faire plus que simplement établir un lien ailleurs. Peut-être fournissez une ou deux phrases ou des éléments énumérés donnant l'essentiel. Vous pouvez créer un lien, mais vous souhaitez apporter une petite valeur supplémentaire. Sinon, vous voudrez peut-être simplement commenter et y mettre le lien.
DWright
le lien dans cette réponse vaut la peine d'être lu, il fournit de bonnes indications sur le moment d'utiliser OSIV et non
ams
1

Je suis v. Rusty sur Hibernate .. mais je pense qu'il est possible d'avoir plusieurs transactions dans une session Hibernate. Ainsi, vos limites de transaction ne doivent pas être les mêmes que les événements de démarrage / arrêt de session.

OSIV, imo, est principalement utile car nous pouvons éviter d'écrire du code pour démarrer un 'contexte de persistance' (aka session) chaque fois que la requête doit faire un accès à la base de données.

Dans votre couche de service, vous devrez probablement faire des appels à des méthodes qui ont des besoins de transaction différents, tels que «Requis, Nouveau requis, etc.» La seule chose dont ces méthodes ont besoin 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 de mise en veille prolongée pour ce fil .. Je dois faire un peu Trucs DB ".

rjk2008
la source
1

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 à cause d'OpenSessionInView et de nombreuses entités chargées, car elles restent dans le cache Hibernate level1 et ne sont pas collectées par la mémoire (je charge beaucoup d'entités avec 500 éléments par page, mais toutes les entités restent dans le cache)

Sébastien Lorber
la source