Cet article décrit la technologie de chargement de latence hibernate. Partagez-le pour votre référence, comme suit:
Le chargement paresseux de Hibernae est une technique très courante. Les attributs de collecte des entités seront retardés par défaut et les entités associées aux entités seront également retardées par défaut. HiberNate utilise ce chargement retardé pour réduire les frais généraux de mémoire du système, garantissant ainsi les performances de fonctionnement de Hibernate.
Analyons d'abord le «secret» du chargement de retard d'hibernate.
Chargement paresseux des propriétés de collecte
Lorsque Hibernate initialise une entité persistante de la base de données, l'attribut de collection de cette entité est-il initialisé avec la classe persistante? Si l'attribut de collection contient 100 000 voire des millions d'enregistrements, la rampe de tous les attributs de collecte lors de l'initialisation de l'entité persistante entraînera une forte baisse des performances. Il est tout à fait possible que le système ne soit que d'utiliser certains enregistrements dans les attributs de collecte de la classe persistante, et pas tous les attributs de collecte du tout. De cette façon, il n'est pas nécessaire de charger tous les attributs de collecte à la fois.
Les stratégies de chargement paresseuses sont généralement recommandées pour les propriétés de collecte. Le chargement dit que retardé consiste à charger les données associées de la base de données lorsque le système doit utiliser les attributs de collecte.
Par exemple, la classe de personne suivante détient un attribut de collection, et l'élément de l'attribut de collection a l'adresse de type, et l'extrait de code de la classe de personne est le suivant:
Listing 1. Person.java
Personne de classe publique {// Identifiez l'attribut ID entier privé; // Nom de la personne Attribut Private String Name; // Gardez l'âge de l'âge de la personne à l'âge privé; // Utiliser SET pour enregistrer l'attribut de collection Set Private <Address> Adresses = new HashSet <Address> (); // Les méthodes de setter et de getter de chaque attribut sont omises ci-dessous ...}Pour que HiberNate gère les propriétés de collecte de la classe persistante, le programme fournit les fichiers de mappage suivants pour la classe persistante:
Listing 2. Person.hbm.xml
<? xml version = "1.0" encoding = "gbk"?> <! doctype hibernate-mapping public "- // hibernate / hibernate mapping dtd 3.0 // en" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping- package = "org.crazyit.app.domain"> <! - mapping personne classe de persistance -> <class name = "personne" table = "personne_inf"> <! - mapping identification id de la propriété -> <id name = "id" chronn = "person_id"> <! - Définir la politique du générateur de clé primaire -> <générateur /> <! name = "Age" type = "int" /> <! - Carte Collection Attributes -> <set name = "adresses" table = "Person_address" lazy = "true"> <! - Spécifiez la colonne de clé étrangère associée -> <key column = "Person_id" /> <Composite-Element> <! - Map Attribut normal Details -> <propriété name = "Detail" /> <! name = "zip" /> </ composite-element> </ set> </sclass> </ hibernate-mapping>
À partir du code ci-dessus qui mappe le fichier, nous pouvons voir que la classe d'adresse dans l'attribut de collection de personne n'est qu'un POJO normal. La classe d'adresse contient deux attributs: détail et zip. Étant donné que le code de classe d'adresse est très simple, le code de cette classe n'est plus donné ici.
Le code dans l'élément <set ... /> dans le fichier de mappage ci-dessus spécifie lazy = "true" (pour <set ... /> élément, lazy = "true" est la valeur par défaut), qui spécifie que Hibernate retardera le chargement de l'objet d'adresse dans l'attribut de collection.
Par exemple, chargez une entité de personne avec ID 1 en suivant le code suivant:
Session Session = sf.getCurrentession (); transaction tx = session.begintransaction (); personne p = (personne) session.get (personne.class, 1); // <1> System.out.println (p.getName ());
Le code ci-dessus doit simplement accéder à l'entité de la personne avec l'ID 1 et ne veut pas accéder à l'objet d'adresse associé à cette entité de personne. Il y a deux situations à ce moment:
1. Si le chargement n'est pas retardé, Hibernate saisira immédiatement l'objet d'adresse associé à l'entité de personne lors du chargement de l'enregistrement de données correspondant à l'entité de la personne.
2. Si le chargement paresseux est utilisé, Hibernate ne chargera que les enregistrements de données correspondant à l'entité de la personne.
Il est évident que la deuxième approche réduit non seulement l'interaction avec la base de données, mais évite également la surcharge de mémoire causée par le chargement des entités d'adresse - c'est aussi pourquoi Hibernate permet le chargement paresseux par défaut.
La question est maintenant: comment le chargement paresseux est-il implémenté? Hibernate Quelle est la valeur de la propriété d'adresse de l'entité de la personne lors du chargement d'une entité de personne?
Pour résoudre ce problème, nous définissons un point d'arrêt au code <1> et le déboguez dans Eclipse. Pour le moment, nous pouvons voir que la fenêtre de la console Eclipse a la sortie comme le montre la figure 1:
Figure 1. Sortie de la console pour les propriétés de collecte de chargement paresseuses
Comme le montre la sortie de la figure 1, Hibernate ne saisit que les données du tableau de données correspondant à l'entité de la personne et ne saisit pas les données du tableau de données correspondant à l'objet d'adresse. C'est un chargement paresseux.
Alors, quelle est la propriété adresse de l'entité personnelle? À l'heure actuelle, vous pouvez voir les résultats illustrés à la figure 2 à partir de la fenêtre des variables d'Eclipse:
Figure 2. Valeurs d'attribut de collecte chargés paresseux
À partir du contenu de la case de la figure 2, on peut voir que la propriété adresse n'est pas les classes d'implémentation familières telles que HashSet et Treeset, mais une classe d'implémentation PersistantSet, qui est une classe d'implémentation fournie par Hibernate pour l'interface définie.
L'objet de collecte PersistantsSet ne capture pas vraiment les données de la table de données sous-jacente, il est donc naturellement impossible d'initialiser vraiment l'objet d'adresse dans la collecte. Cependant, la collection PersistantSet détient un attribut de session, qui est la session Hibernate. Lorsque le programme doit accéder à l'élément de collecte PersistantsTet, le PersistantsTer utilisera cet attribut de session pour saisir les enregistrements de données correspondant à l'objet d'adresse réel.
Alors, que saisissez-vous exactement les enregistrements de données correspondant à ces entités d'adresse? Ce n'est pas difficile pour PersistantSet, car il y a aussi un attribut de propriétaire dans la collection PersistantsSet, qui indique l'entité personnelle à laquelle appartient l'objet d'adresse. HiberNate recherchera les données du tableau de données correspondant à l'adresse correspondant au tableau de données.
Par exemple, nous cliquons sur la ligne d'adresses dans la fenêtre illustrée à la figure 2, ce qui signifie que nous disons à Eclipse de déboguer et de sortir l'attribut d'adresses. Il s'agit d'accéder à l'attribut d'adresses. À l'heure actuelle, vous pouvez voir les instructions SQL suivantes dans la fenêtre de la console Eclipse:
Sélectionnez Adresses0_.Person_Id en tant que personne1_0_0_, adresses0_.detail comme Details0_, adresses0_.zip en tant que zip0_from Person_address ADRESSES0_WORD ADRESSES0_.PERSON_ID =?
Il s'agit de la collection Persistants et des instructions SQL qui capturent des enregistrements d'adresse spécifiques en fonction de l'attribut propriétaire. À l'heure actuelle, vous pouvez voir la sortie illustrée à la figure 3 de la fenêtre des variables d'Eclipse:
Figure 3. Valeurs d'attribut de collecte chargés
Comme le montre la figure 3, l'attribut d'adresses à l'heure actuelle a été initialisé, et l'ensemble contient 2 objets d'adresse, qui sont les deux objets d'adresse associés à l'entité de la personne.
D'après l'introduction ci-dessus, nous pouvons voir que la clé pour retarder le chargement des attributs définis de Hibernate réside dans la classe d'implémentation PersistantsSet. Pendant le chargement paresseux, la collection persistante ne contient aucun éléments. Cependant, PersistantsSet tiendra une session Hibernate, qui peut garantir que lorsque le programme doit accéder à la collecte, l'enregistrement de données est chargé "immédiatement" et charger les éléments de collecte.
Semblable à la classe d'implémentation PersistantsSet, HiberNate fournit également PersistantList, PersistantMap, PersistantsOredMap, PersistantsRedSet et d'autres classes de mise en œuvre, et leurs fonctions sont à peu près similaires à celles de PersistantSet.
Les lecteurs qui connaissent les attributs de collection Hibernate doivent se souvenir: Hibernate exige que les attributs de collecte de déclarations ne puissent être utilisés qu'avec des interfaces telles que Set, List, Map, SortEdSet, tridmap, etc., et ne peuvent pas être implémentées à l'aide de classes de hashSet, ArrayList, Hashmap, Treeset, Treemap et d'autres classes d'implémentation. The reason is that Hibernate needs to delay loading the collection attributes, and the delay loading of Hibernate relies on PersistentSet, PersistentList, PersistentMap, PersistentSortedMap, and PersistentSortedSet to complete - that is, the underlying Hibernate needs to use its own collection implementation class to complete the lazy loading, so it requires developers to use the collection interface, rather than the collection implementation class to declare collection attributs.
HiberNate utilise le chargement paresseux pour les attributs de collecte par défaut. Dans certains cas particuliers, définissez l'attribut lazy = "false" pour des éléments tels que <set ... />, <list ... />, <map ... /> pour annuler le chargement paresseux.
Retour charge des entités associées
Par défaut, HiberNate utilisera également le chargement paresseux pour charger l'entité associée. Qu'il s'agisse d'une association un-à-plusieurs, d'une association individuelle ou d'une association plusieurs à plusieurs, Hibernate utilisera le chargement paresseux par défaut.
Pour les entités associées, elles peuvent être divisées en deux cas:
1. Lorsqu'une entité associée est plusieurs entités (y compris un à plusieurs, plusieurs à plusieurs): Pour le moment, l'entité associée existera sous la forme d'une collection, et Hibernate utilisera PersistantsSet, PersistantList, PersistentMap, PersistantSortedMap, Persistents. C'est la situation introduite plus tôt.
2. Lorsqu'une entité associée est une entité unique (y compris un à un et plusieurs à un): lorsque Hibernate charge une entité, l'entité associée retardée sera un objet proxy généré dynamiquement.
Lorsque l'entité associée est une entité unique, c'est-à-dire lorsque l'entité associée est mappée en utilisant <plusieurs à un ... /> ou <un à un ... />, ces deux éléments peuvent également spécifier le chargement paresseux via l'attribut paresseux.
L'exemple suivant mappe également la classe d'adresse à une classe persistante. À l'heure actuelle, la classe d'adresses devient également une classe d'entité, et l'entité de la personne et l'entité d'adresse forment une association bidirectionnelle un à plusieurs. Le code de fichier de mappage à l'heure actuelle est le suivant:
Listing 3. Person.hbm.xml
<? xml version = "1.0" Encoding = "gbk"?> <! - Spécifiez les informations DTD pour Hibernate -> <! Doctype Hibernate-Mapping public "- // Hibernate / Hibernate Mapping mapping dtd 3.0 // en "" http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd "> <Hibernate-Mapping package =" org.crazyit.app.domain "> <! - Mapping Person Persistence Class -> <class Name =" Person "Table =" Person_inf "> <! Column = "Person_id"> <! - Définir la stratégie du générateur de clés primaires -> <générateur /> <! - Utilisé pour mapper les attributs communs -> <propriété name = "name" type = "String" /> <propriété name = "Age" type = "int" /> <! - Map Collection Attributes inverse = "true"> <! - Spécifiez la colonne de clé étrangère associée -> <key column = "Person_id" /> <! - Utilisé pour mapper aux attributs de classe associés -> <one-to-many /> </set> </ class> <! - Map Address Persister Class -> <class named = "Address" Tableau = "Address"> <! - Map Identification Attribute AddredId -> <id name = "Addred" Column = "Address_id"> <! - Spécifiez la stratégie du générateur de clé primaire -> <générateur /> </ id> <! - MAP Normal Attribut Detail -> <propriété name = "Detail" /> <! - Map Attribut Normal Zip -> <Propriété Name = "Zip" /> <! - Le nom de la colonne doit être spécifié en tant qu'entité Person_id - qui est le même à la valeur d'attribut de colonne de l'élément clé dans l'entité associée -> name = "Person" Column = "Person_id" not-null = "true" /> </ class> </ hibernate-mapping>
Ensuite, le programme charge l'entité de la personne avec ID 1 via l'extrait de code suivant:
// Ouvrez la session de sessions dépendante du contexte = SF.GetCurrentession (); transaction tx = session.begintransaction (); adresse Adresse = (adresse) session.get (adresse.class, 1); // <1> System.out.println (adresse.GetDetail ());
Afin de voir le traitement de l'entité associée d'Hibernate lors du chargement de l'entité d'adresse, nous définissons un point d'arrêt au code <1> et le déboguez dans Eclipse. Pour le moment, nous pouvons voir que la fenêtre de la console Eclipse sortira l'instruction SQL suivante:
Sélectionnez Adresse0_.Address_id As Address1_1_0_, Address0_.detail As Detail1_0_, Address0_.zip As Zip1_0_, Address0_.Serson_Id As Person4_1_0_ From Address_inf Address0_where Address0_.Address_id =?
Il n'est pas difficile de voir à partir de cette instruction SQL que Hibernate charge le tableau de données correspondant à l'entité d'adresse pour faire des enregistrements, mais ne fait pas d'exploration de dossiers du tableau de données correspondant à l'entité de la personne, à savoir que le chargement paresseux joue un rôle.
À partir de la fenêtre des variables d'Eclipse, voir la sortie illustrée à la figure 4:
Figure 4. Entité de chargement retardée
On peut voir clairement sur la figure 4 que l'entité de personne associée à l'entité d'adresse n'est pas un objet de personne, mais une instance de la classe _ $$ _ Javassist_0. Cette classe est une classe proxy générée dynamiquement par Hibernate à l'aide du projet Javassist. Lorsque Hibernate retarde le chargement de l'entité associée, Javassist sera utilisé pour générer un objet proxy dynamique, et cet objet proxy sera responsable de la proxyation du "non chargé".
Tant que l'application doit utiliser une entité associée qui n'est pas encore chargée ", l'objet proxy de la personne _ $$ _ Javassist_0 sera responsable du chargement de l'entité réelle associée et du retour de l'entité associée réelle - il s'agit du modèle de proxy le plus typique.
Cliquez sur l'attribut de personne dans la fenêtre Variables illustrée à la figure 4 (c'est-à-dire forcer l'attribut de personne à utiliser en mode débogage), puis vous verrez la sortie des instructions SQL suivantes dans la fenêtre de la console d'Eclipse:
Sélectionnez Person0_.Person_Id AS Person1_0_0_, Person0_.name as name0_0_, Person0_.age as Age0_0_ From Person_inf Person0_where Person0_.person_id =?
L'instruction SQL ci-dessus est une instruction qui capture l'entité associée de "chargement de retard". À l'heure actuelle, vous pouvez voir les résultats illustrés à la figure 5 de la sortie de la fenêtre des variables:
Figure 5. Entité chargée
Hibernate adopte le mode "charge retardée" pour gérer les entités associées. En fait, lors du chargement de l'entité principale, il ne saisit pas vraiment les données correspondantes de l'entité associée, mais génère simplement dynamiquement un objet comme proxy de l'entité associée. Lorsqu'une application a vraiment besoin d'utiliser une entité associée, l'objet proxy est responsable de saisir les enregistrements de la base de données sous-jacente et d'initialiser l'entité réelle associée.
Dans le chargement de retard Hibernate, ce que le programme client commence à obtenir est un objet proxy généré dynamiquement, tandis que l'entité réelle est déléguée à l'objet proxy pour la gestion - il s'agit du modèle proxy typique.
Mode agent
Le mode proxy est un mode de conception avec une application très large. Lorsque le code client doit appeler un objet, le client ne se soucie pas de savoir s'il faut obtenir l'objet avec précision. Il n'a besoin que d'un objet qui peut fournir la fonction. Pour le moment, nous pouvons retourner le proxy (proxy) de l'objet.
Dans cette méthode de conception, le système fournira un objet avec un objet proxy et l'objet proxy contrôle la référence à l'objet source. Un proxy est un objet Java qui agit au nom d'un autre objet Java. Dans certains cas, le code client ne veut pas ou ne peut pas appeler directement la Callee, et l'objet proxy peut agir comme intermédiaire entre le client et l'objet cible.
Pour les clients, il ne peut pas distinguer la différence entre un objet proxy et un objet réel, et il n'a pas besoin de distinguer la différence entre un objet proxy et un objet réel. Le code client ne connaît pas l'objet proxy réel. Le code client est orienté vers l'interface et ne contient qu'une interface de l'objet proxy.
En bref, tant que le code client ne peut pas ou ne veut pas accéder directement à l'objet appelé - il existe de nombreuses raisons de cette situation, comme la création d'un objet avec une surcharge système élevée, ou l'objet appelé est sur un hôte distant, ou la fonction de l'objet cible ne suffit pas pour répondre aux besoins ..., mais un objet proxy supplémentaire est créé pour le retourner au client pour une utilisation, donc cette méthode de conception est le mode de proxy.
Ce qui suit démontre un mode proxy simple. Le programme fournit d'abord une interface d'image, représentant l'interface implémentée par un grand objet d'image. Le code d'interface est le suivant:
Listing 3. Image.java
Image d'interface publique {void show ();}Cette interface fournit une classe d'implémentation qui simule un grand objet d'image, et le constructeur de la classe d'implémentation utilise la méthode thread.sleep () pour faire une pause 3. Vous trouverez ci-dessous le code du programme pour le bigimage.
Listing 4. Bigimage.java
// utilise ce bigImage pour simuler une grande image de classe publique BigImage implémente l'image {public bigImage () {try {// Program Pauses 3S Mode System System Overhead Thread.Sleep (3000); System.out.println ("Chargement d'image Succès avec succès ...");} catch (InterruptedException ex) {ex.printStackTrace ();}} // implémenter la méthode show () dans l'image publique void show () {System.out.println ("Draw the réel grande image");}}}}}}}}Le code de programme ci-dessus fait référence à 3S, ce qui indique qu'il faut 3 ans au-dessus pour créer un objet BigImage - le programme utilise ce retard pour simuler la surcharge du système causée par le chargement de cette image. Si le mode proxy n'est pas utilisé, le système générera un retard 3S lorsque BigImage sera créé dans le programme. Pour éviter ce retard, le programme fournit un objet proxy pour l'objet BigImage, et la classe proxy de la classe BigImage est la suivante.
Listing 5. ImageProxy.java
classe publique ImageProxy implémente l'image {// combiner une instance d'image comme image d'image privée d'objet proxy; // Utilisez des entités abstraites pour initialiser l'objet proxy public imageproxy (image d'image) {this.image = image;} / *** réécriture la méthode show () de l'interface d'image * Cette méthode est utilisée pour contrôler l'objectif de proxy, * et est responsable de la création et de la création de l'objet de proxy * show () {// Créer l'objet proxy uniquement si (image == null) {image = new bigImage ();} image.show ();}}La classe proxy ImageProxy ci-dessus implémente la même méthode Show () que BigImage, qui permet au code client d'utiliser l'objet proxy comme BigImage après avoir obtenu l'objet proxy.
La logique de contrôle est ajoutée à la méthode Show () de la classe ImageProxy. Cette logique de contrôle est utilisée pour contrôler que l'objet BigImage Proxy ne sera créé que lorsque le système appelle réellement le show () de l'image. Le programme suivant doit utiliser l'objet BigImage, mais le programme ne renvoie pas directement l'instance BigImage, mais renvoie d'abord l'objet proxy BigImage, comme indiqué dans le programme suivant.
Listing 6. BigImageTest.java
classe publique BigImageTest {public static void main (String [] args) {long start = System.currenttimemillis (); // Le programme renvoie un objet image, qui est juste l'objet proxy de l'image de BigImage Image = New ImageProxy (null); System.out.println ("The Time Of the System Objective The Image Object:" + (System.CurrentImmils () - Démarrer)); // Le programme créera réellement l'objet proxy lorsque la méthode Show () du proxy d'image est réellement appelée. image.show ();}}Le programme ci-dessus initialise l'image très rapidement parce que le programme ne crée pas vraiment l'objet BigImage, mais obtient simplement l'objet Proxy ImageProxy - jusqu'à ce que le programme appelle la méthode Image.show (), le programme doit réellement appeler la méthode Show () de l'objet BigImage, et le programme crée réellement l'objet BigImage à ce moment-là. Exécutez le programme ci-dessus et voyez les résultats illustrés à la figure 6.
Figure 6. Améliorer les performances à l'aide du mode proxy
En voyant les résultats en cours montrés dans la figure 6, les lecteurs devraient pouvoir convenir que l'utilisation du mode proxy améliore les performances du système de l'obtention d'objets d'image. Mais certains lecteurs peuvent poser des questions: lorsqu'un programme appelle la méthode Show () de l'objet ImageProxy, il doit également créer un objet BigImage, mais la surcharge du système n'a pas été vraiment réduite? C'est juste que ce système de surcharge du système est retardé?
Nous pouvons répondre à cette question des deux perspectives suivantes:
Le retard de la création de BigImage jusqu'à ce qu'il soit vraiment nécessaire peut assurer le fonctionnement fluide du programme précédent et réduire le temps de survie de BigImage en mémoire, en économisant les frais généraux de la mémoire du système d'un point de vue macro.
Dans certains cas, le programme n'appellera peut-être jamais la méthode Show () de l'objet ImageProxy - ce qui signifie que le système n'a pas du tout besoin de créer un objet BigImage. Dans ce cas, l'utilisation du mode proxy peut considérablement améliorer les performances du fonctionnement du système.
Totalement similaire, HiberNate utilise également le mode proxy pour "retarder" le temps de charger l'entité associée. Si le programme n'a pas besoin d'accéder à l'entité associée, le programme n'engagera pas l'entité associée. Cela peut enregistrer la surcharge de mémoire du système et raccourcir l'heure où Hibernate charge l'entité.
résumé
Hibernate Lazy Load est essentiellement une application du mode proxy. Au cours des dernières années, nous avons souvent utilisé le mode proxy pour réduire les frais généraux de la mémoire du système et améliorer les performances de l'application. Hibernate tire parti de cet avantage du mode proxy et combine Javassist ou CGLIB pour générer dynamiquement des objets proxy, ce qui ajoute de la flexibilité au mode proxy. Hibernate donne à cette utilisation un nouveau nom: chargement paresseux. Dans tous les cas, l'analyse et la compréhension complètes de la mise en œuvre de ces cadres open source peuvent mieux ressentir les avantages des modèles de conception classiques.
J'espère que la description de cet article sera utile à la programmation Java de chacun basée sur le cadre d'hibernate.