Conception d'applications à chargement différé Hibernate

87

J'ai tendance à utiliser Hibernate en combinaison avec le framework Spring et ses capacités de démarcation de transaction déclarative (par exemple, @Transactional ).

Comme nous le savons tous, la mise en veille prolongée essaie d'être aussi non invasive et transparente que possible, mais cela s'avère un peu plus difficile lors de l'utilisation de lazy-loadedrelations.


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

  1. Faites des relations non paresseuses (par exemple, fetchType=FetchType.EAGER)
    • Cela vioalite toute l'idée du chargement paresseux.
  2. Initialiser les collections en utilisant Hibernate.initialize(proxyObj);
    • Cela implique un couplage relativement élevé au DAO
    • Bien que nous puissions définir une interface avec initialize, les autres implémentations ne sont pas garanties de fournir un équivalent.
  3. Ajouter un comportement de transaction aux Modelobjets persistants eux-mêmes (à l'aide d' un proxy dynamique ou @Transactional)
    • Je n'ai pas essayé l'approche proxy dynamique, même si je n'ai jamais semblé faire fonctionner @Transactional sur les objets persistants eux-mêmes. Probablement à cause de cette mise en veille prolongée est l'opération sur un proxy avec lequel bein.
    • Perte de contrôle lorsque des transactions ont effectivement lieu
  4. Fournir à la fois une API paresseuse / non paresseuse, par exemple, loadData()etloadDataWithDeps()
    • Force l'application à savoir quand utiliser quelle routine, encore une fois couplage serré
    • Débordement de méthode,, loadDataWithA()....,loadDataWithX()
  5. Forcer la recherche de dépendances, par exemple en fournissant uniquement des byId()opérations
    • Nécessite beaucoup de routines non orientées objet, par exemple findZzzById(zid), et ensuite getYyyIds(zid)au lieu dez.getY()
    • Il peut être utile de récupérer chaque objet d'une collection un par un s'il y a une surcharge de traitement importante entre les transactions.
  6. Faire partie de l' application @Transactional au lieu de seulement le DAO
    • Considérations possibles sur les transactions imbriquées
    • Nécessite des routines adaptées à la gestion des transactions (par exemple, suffisamment petites)
    • Petit impact programmatique, bien qu'il puisse entraîner de grosses transactions
  7. Fournir au DAO des profils de récupération dynamiques , par exempleloadData(id, fetchProfile);
    • Les applications doivent savoir quel profil utiliser quand
  8. Type de transactions AoP, par exemple, intercepter des opérations et effectuer des transactions si nécessaire
    • Nécessite une manipulation de code d'octet ou l'utilisation d'un proxy
    • Perte de contrôle lors 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 des lazy-loadedrelations dans la conception de votre application?

(Oh, et désolé pour WoT )

Johan Sjöberg
la source
exemple pour les options 2 et 5: m-hewedy.blogspot.ch/2010/03/…
Adrien Be
Pourriez-vous donner un exemple pour l'option 4?
degrésightdc

Réponses:

26

Comme nous le savons tous, l'hibernation essaie d'être aussi non invasive et transparente que possible

Je dirais que l'hypothèse initiale est fausse. La persistance transaparente est un mythe, car l'application doit toujours prendre soin du cycle de vie de l'entité et de la taille du graphe d'objets en cours de chargement.

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 d'Hibernate d'une manière ou d'une autre.

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

axtavt
la source
Vous avez raison bien sûr, le plus transparent possible ne fonctionne que jusqu'à présent. Ce sont de bons choix que vous avez choisis.
Johan Sjöberg
IMHO: réponse parfaitement correcte. En effet, c'est un mythe. BTW: mon vote serait pour les options 4 et 7 (ou s'éloigner du tout de l'ORM)
G.Demecki
7

Je ne sais pas à 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 mes propres caches d'application. Cas typique:

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

Donc, pour résoudre ce problème, nous avons un certain nombre de règles:

  • envelopper les sessions de manière aussi transparente que possible (par exemple OpenSessionInViewFilterpour les applications Web);
  • avoir une API commune pour les threads / pools de threads où la liaison / détachement de la session db est effectuée quelque part en haut de la hiérarchie (encapsulée try/finally) afin que les sous-classes n'aient pas à y penser;
  • lors du passage d'objets entre les threads, transmettez les ID au lieu des objets eux-mêmes. Le thread de réception peut charger un objet s'il en a besoin;
  • lors de la mise en cache d'objets, ne mettez jamais en cache des objets mais leurs identifiants. Ayez une méthode abstraite dans votre classe DAO ou gestionnaire pour charger l'objet à partir du cache Hibernate de 2e niveau lorsque vous connaissez l'ID. Le coût de récupération des objets à partir du cache Hibernate de 2e niveau est encore beaucoup moins cher que d'aller à DB.

Ceci, comme vous pouvez le voir, est en effet loin d'être non invasif et transparent . Mais le coût est toujours supportable, à comparer avec le prix que je devrais payer pour un chargement impatient. Le problème avec ce dernier est que cela 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 du processeur et la latence pour ne citer que le moins sont également bien pires, donc je suppose que je peux vivre avec.

mindas
la source
Merci pour votre réponse. La perte de transparencyest de forcer l'application à se soucier du chargement des objets paresseux. Si tout était récupéré avec empressement, l'application pourrait ne pas savoir si les objets sont conservés dans une base de données ou non, car Foo.getBar()cela réussira toujours. > when passing objects between threads, pass IDs, oui, cela correspondrait au n ° 5.
Johan Sjöberg
3

Un modèle très courant consiste à utiliser OpenEntityManagerInViewFilter si vous créez une application Web.

Si vous créez 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 Lazy Load". Si vous avez besoin de quelque chose de plus avancé pour le réglage des performances, je pense que la recherche de profils est la voie à suivre.

Augusto
la source
1
Je suppose que vous vouliez dire: Un très courant ... antimodèle . Bien que je serais d'accord avec l'ouverture de TX au niveau du service, l'utilisation du OSIVest toujours un anti-modèle et conduit à des problèmes très graves tels que l'incapacité de gérer gracieusement les exceptions ou la dégradation des performances. Pour résumer: IMHO OSIV est une solution facile à vivre, mais bonne uniquement pour un jouet-projets.
G.Demecki