86 votes

Conception d'une application Hibernate lazy-load

J'ai tendance à utiliser Hibernate en combinaison avec Printemps et ses capacités de démarcation déclarative des transactions (par ex, @Transactional ).

Comme nous le savons tous, Hibernate essaie d'être aussi non invasif et comme transparent que possible, mais cela prouve un peu plus difficile lors de l'utilisation lazy-loaded relations.


Je vois un certain nombre d'alternatives de conception avec différents niveaux de transparence.

  1. Faire en sorte que les relations ne soient pas chargées paresseusement (par ex, fetchType=FetchType.EAGER)
    • Cela vicie l'idée même du chargement paresseux
  2. Initialiser les collections en utilisant Hibernate.initialize(proxyObj);
    • Cela implique un couplage relativement élevé avec le DAO.
    • Bien que nous puissions définir une interface avec initialize les autres implémentations ne sont pas garanties de fournir un équivalent.
  3. Ajout d'un comportement de transaction au système persistant Model eux-mêmes (en utilisant soit proxy dynamique o @Transactional )
    • Je n'ai pas essayé l'approche du proxy dynamique, bien que je n'aie jamais réussi à faire fonctionner @Transactional sur les objets persistants eux-mêmes. C'est probablement dû au fait qu'Hibernate fonctionne avec un proxy.
    • Perte de contrôle au moment où les transactions ont lieu
  4. Fournir une API paresseuse/non paresseuse, par ex, loadData() y loadDataWithDeps()
    • Oblige l'application à savoir quand utiliser telle ou telle routine, ce qui constitue un couplage étroit.
    • Dépassement de méthode, loadDataWithA() , ...., loadDataWithX()
  5. Forcer la recherche de dépendances, par exemple en fournissant seulement byId() opérations
    • Nécessite beaucoup de routines non orientées objet, par exemple, findZzzById(zid) et ensuite getYyyIds(zid) au lieu de z.getY()
    • Il peut être utile d'extraire chaque objet d'une collection un par un si les frais de traitement entre les transactions sont importants.
  6. Faire une partie de la application @Transactional au lieu de la seule DAO
    • Considérations possibles sur les transactions imbriquées
    • Nécessite des routines adaptées à la gestion des transactions (par exemple, suffisamment petites)
    • Faible impact programmatique, bien qu'il puisse entraîner des transactions importantes.
  7. Fournir à la DAO des données dynamiques Récupérer des profils par exemple, loadData(id, fetchProfile);
    • Les applications doivent savoir quel profil utiliser lorsque
  8. Type d'opérations de l'AoP, par exemple, intercepter des opérations et effectuer des transactions si nécessaire.
    • Nécessite la manipulation du code d'octet ou l'utilisation d'un proxy.
    • Perte de contrôle lors de l'exécution des transactions
    • Magie noire, comme toujours :)

Ai-je manqué une option ?


Quelle est votre approche préférée lorsque vous essayez de minimiser l'impact de lazy-loaded dans la conception de votre application ?

(Oh, et désolé pour WoT )

26voto

axtavt Points 126632

Comme nous le savons tous, Hibernate essaie d'être le moins invasif et le plus transparent possible.

Je dirais que l'hypothèse initiale est fausse. La persistance transaparente est un mythe, puisque l'application doit toujours s'occuper du cycle de vie des entités et de la taille du graphe d'objets chargé.

Notez qu'Hibernate ne peut pas lire les pensées, donc si vous savez que vous avez besoin d'un ensemble particulier de dépendances pour une opération particulière, vous devez exprimer vos intentions à Hibernate d'une manière ou d'une autre.

De ce point de vue, les solutions qui expriment explicitement ces intentions (à savoir les solutions 2, 4 et 7) semblent raisonnables et ne souffrent pas du manque de transparence.

7voto

mindas Points 14217

Je ne sais pas exactement à quel problème (causé par la paresse) vous faites allusion, mais pour moi le plus gros problème est d'éviter de perdre le contexte de session dans les caches de mes propres applications. C'est un cas typique :

  • objet foo est chargé et placé dans une carte ;
  • un autre thread prend cet objet dans la carte et appelle foo.getBar() (quelque chose qui n'a jamais été appelé auparavant et qui est évalué paresseusement) ;
  • boom !

Pour y remédier, nous avons donc mis en place un certain nombre de règles :

  • envelopper les sessions de la manière la plus transparente possible (par ex. OpenSessionInViewFilter pour les webapps) ;
  • ont une API commune pour les threads/thread pools où la liaison/désolidarisation de la session de BD est effectuée quelque part en haut de la hiérarchie (enveloppée dans des try/finally ) afin que les sous-classes n'aient pas à y penser ;
  • lors du passage d'objets entre les threads, passez les identifiants au lieu des objets eux-mêmes. Le thread récepteur peut charger l'objet s'il en a besoin ;
  • lors de la mise en cache des objets, ne mettez jamais en cache les objets mais leurs identifiants. Prévoyez une méthode abstraite dans votre DAO ou votre classe de gestionnaire pour charger l'objet depuis le cache Hibernate de deuxième niveau lorsque vous connaissez son ID. Le coût de la récupération des objets à partir du cache d'Hibernate de deuxième niveau est toujours bien moins élevé que celui de l'accès à la base de données.

Ceci, comme vous pouvez le voir, est en effet loin d'être proche de non invasive et transparente . Mais le coût est encore supportable, à comparer avec le prix que je devrais payer pour un chargement empressé. Le problème avec ce dernier est qu'il conduit parfois à l'effet papillon lors du chargement d'un seul objet référencé, sans parler d'une collection d'entités. La consommation de mémoire, l'utilisation de l'unité centrale et la latence, pour ne citer qu'elles, sont également bien pires, donc je suppose que je peux m'en accommoder.

3voto

Augusto Points 14531

Un modèle très courant consiste à utiliser OpenEntityManagerInViewFilter si vous construisez une application web.

Si vous construisez un service, j'ouvrirais le TX sur la méthode publique du service, plutôt que sur les DAO, car très souvent une méthode nécessite d'obtenir ou de mettre à jour plusieurs entités.

Cela résoudra toute "exception de chargement paresseux". Si vous avez besoin de quelque chose de plus avancé pour l'optimisation des performances, je pense que les profils de récupération sont la solution.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X