This article describes Hibernate latency loading technology. Share it for your reference, as follows:
Hibernae's lazy loading is a very common technique. The collection attributes of entities will be delayed by default, and the entities associated with entities will also be delayed by default. Hibernate uses this delayed loading to reduce the system's memory overhead, thereby ensuring the operating performance of Hibernate.
Let’s first analyze the “secret” of Hibernate delay loading.
Lazy loading of collection properties
When Hibernate initializes a persistent entity from the database, is the collection attribute of that entity initialized with the persistent class? If the collection attribute contains 100,000 or even millions of records, the crawling of all collection attributes while initializing the persistent entity will result in a sharp decline in performance. It is entirely possible that the system only needs to use some records in the collection attributes of the persistent class, and not all of the collection attributes at all. In this way, there is no need to load all collection attributes at once.
Lazy loading strategies are generally recommended for collection properties. The so-called delayed loading is to load associated data from the database when the system needs to use collection attributes.
For example, the following Person class holds a collection attribute, and the element in the collection attribute has the type Address, and the code snippet of the Person class is as follows:
Listing 1. Person.java
public class Person{ // Identify the attribute private Integer id; // Person's name attribute private String name; // Keep Person's age attribute private int age; // Use Set to save the collection attribute private Set<Address> addresses = new HashSet<Address>(); // The setter and getter methods of each attribute are omitted below...}In order for Hibernate to manage the collection properties of the persistent class, the program provides the following mapping files for the persistent class:
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 Person Persistence Class--><class name="Person" table="person_inf"><!-- Mapping Identification Property ID --><id name="id" column="person_id"><!-- Define primary key generator policy --><generator/><!-- Used to map common attributes --><property name="name" type="string"/><property name="age" type="int"/><!-- Map collection attributes --><set name="addresses" table="person_address" lazy="true"><!-- Specify the associated foreign key column --><key column="person_id"/><composite-element><!-- Map normal attribute details --><property name="detail"/><!-- Map normal attribute zip --><property name="zip"/></composite-element></set></class></hibernate-mapping>
From the above code that maps the file, we can see that the Address class in Person's collection attribute is just a normal POJO. The Address class contains two attributes: detail and zip. Since the Address class code is very simple, the code for this class is no longer given here.
The code in the <set.../> element in the above mapping file specifies lazy="true" (for <set.../> element, lazy="true" is the default value), which specifies that Hibernate will delay loading the Address object in the collection attribute.
For example, load a Person entity with ID 1 by following the following code:
Session session = sf.getCurrentSession();Transaction tx = session.beginTransaction();Person p = (Person) session.get(Person.class, 1); //<1>System.out.println(p.getName());
The above code just needs to access the Person entity with ID 1, and does not want to access the Address object associated with this Person entity. There are two situations at this time:
1. If loading is not delayed, Hibernate will immediately grab the Address object associated with the Person entity when loading the data record corresponding to the Person entity.
2. If lazy loading is used, Hibernate will only load the data records corresponding to the Person entity.
It is obvious that the second approach not only reduces interaction with the database, but also avoids the memory overhead caused by loading Address entities - this is also why Hibernate enables lazy loading by default.
The question now is, how is lazy loading implemented? Hibernate What is the addresses property value of the Person entity when loading a Person entity?
To solve this problem, we set a breakpoint at the code <1> and debug it in Eclipse. At this time, we can see that the Eclipse Console window has the output as shown in Figure 1:
Figure 1. Console output for lazy loading collection properties
As shown in the output in Figure 1, Hibernate only grabs data from the data table corresponding to the Person entity, and does not grab data from the data table corresponding to the Address object. This is lazy loading.
So what is the addresses property of the Person entity? At this time, you can see the results shown in Figure 2 from the Variables window of Eclipse:
Figure 2. Lazy loaded collection attribute values
From the content in the box in Figure 2, it can be seen that the addresses property is not the familiar implementation classes such as HashSet and TreeSet, but a PersistentSet implementation class, which is an implementation class provided by Hibernate for the Set interface.
The PersistentSet collection object does not really capture the data of the underlying data table, so it is naturally impossible to really initialize the Address object in the collection. However, the PersistentSet collection holds a session attribute, which is the Hibernate Session. When the program needs to access the PersistentSet collection element, the PersistentSet will use this session attribute to grab the data records corresponding to the actual Address object.
So what exactly do you grab the data records corresponding to those Address entities? This is not difficult for PersistentSet, because there is also an owner attribute in the PersistentSet collection, which indicates the Person entity to which the Address object belongs. Hibernate will search for the data from the data table corresponding to the Address corresponding to the data table.
For example, we click the addresses line in the window shown in Figure 2, which means we tell Eclipse to debug and output the addresses attribute. This is to access the addresses attribute. At this time, you can see the following SQL statements in the Eclipse Console window:
select addresses0_.person_id as person1_0_0_, addresses0_.detail as details0_, addresses0_.zip as zip0_from person_address addresses0_where addresses0_.person_id=?
This is the PersistentSet collection and SQL statements that capture specific Address records according to the owner attribute. At this time, you can see the output shown in Figure 3 from the Variables window of Eclipse:
Figure 3. Loaded collection attribute values
As can be seen from Figure 3, the addresses attribute at this time has been initialized, and the set contains 2 Address objects, which are the two Address objects associated with the Person entity.
From the above introduction, we can see that the key to delay loading of Set attributes of Hibernate lies in the PersistentSet implementation class. During lazy loading, the PersistentSet collection does not hold any elements. However, PersistentSet will hold a Hibernate Session, which can ensure that when the program needs to access the collection, the data record is loaded "immediately" and load the collection elements.
Similar to the PersistentSet implementation class, Hibernate also provides PersistentList, PersistentMap, PersistentSortedMap, PersistentSortedSet and other implementation classes, and their functions are roughly similar to those of PersistentSet.
Readers who are familiar with Hibernate collection attributes should remember: Hibernate requires that declare collection attributes can only be used with interfaces such as Set, List, Map, SortedSet, SortedMap, etc., and cannot be implemented using HashSet, ArrayList, HashMap, TreeSet, TreeMap and other implementation classes. 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 attributes.
Hibernate uses lazy loading for collection attributes by default. In some special cases, set the lazy="false" attribute for elements such as <set.../>, <list.../>, <map.../> to cancel lazy loading.
Delay loading of associated entities
By default, Hibernate will also use lazy loading to load the associated entity. Whether it is a one-to-many association, a one-to-one association, or a many-to-many association, Hibernate will use lazy loading by default.
For associated entities, they can be divided into two cases:
1. When an associated entity is multiple entities (including one-to-many, many-to-many): At this time, the associated entity will exist in the form of a collection, and Hibernate will use PersistentSet, PersistentList, PersistentMap, PersistentSortedMap, PersistentSortedSet and other collections to manage lazy loading entities. This is the situation introduced earlier.
2. When an associated entity is a single entity (including one-to-one and many-to-one): When Hibernate loads an entity, the delayed associated entity will be a dynamically generated proxy object.
When the associated entity is a single entity, that is, when the associated entity is mapped using <many-to-one.../> or <one-to-one.../>, these two elements can also specify lazy loading through the lazy attribute.
The following example also maps the Address class to a persistent class. At this time, the Address class also becomes an entity class, and the Person entity and the Address entity form a one-to-many two-way association. The mapping file code at this time is as follows:
Listing 3. Person.hbm.xml
<?xml version="1.0" encoding="GBK"?><!-- Specify the DTD information for Hibernate--><!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 Person Persistence Class--><class name="Person" table="person_inf"><!-- Mapping Identification Property ID --><id name="id" column="person_id"><!-- Define primary key generator policy --><generator/><!-- Used to map common attributes --><property name="name" type="string"/><property name="age" type="int"/><!-- Map collection attributes, the collection element is another persistent entity that does not specify a cascade attribute, specify that the association relationship is not controlled --><set name="addresses" inverse="true"><!-- Specify the associated foreign key column --><key column="person_id"/><!-- Used to map to associated class attributes --><one-to-many/></set></class><!-- Map Address persistent class --><class name="Address" table="address_inf"><!-- Map Identification Attribute AddressId --><id name="addressId" column="address_id"><!-- Specify primary key generator policy--><generator/></id><!-- Map Normal attribute detail --><property name="detail"/><!-- Map Normal attribute zip --><property name="zip"/><!-- The column name must be specified as person_id, which is the same as the column attribute value of the key element in the associated entity--><many-to-one name="person"column="person_id" not-null="true"/></class></hibernate-mapping>
Next, the program loads the Person entity with ID 1 through the following code snippet:
// Open the context-dependent SessionSession session = sf.getCurrentSession();Transaction tx = session.beginTransaction();Address address = (Address) session.get(Address.class , 1); //<1>System.out.println(address.getDetail());
In order to see the processing of Hibernate's associated entity when loading the Address entity, we set a breakpoint at the code <1> and debug it in Eclipse. At this time, we can see that the Eclipse Console window outputs the following SQL statement:
select address0_.address_id as address1_1_0_, address0_.detail as detail1_0_, address0_.zip as zip1_0_, address0_.person_id as person4_1_0_ from address_inf address0_where address0_.address_id=?
It is not difficult to see from this SQL statement that Hibernate loads the data table corresponding to the Address entity to crawl records, but does not crawl records from the data table corresponding to the Person entity, which is that lazy loading plays a role.
From the Variables window of Eclipse, see the output shown in Figure 4:
Figure 4. Delayed loading entity
It can be clearly seen from Figure 4 that the Person entity associated with the Address entity is not a Person object, but an instance of the Person_$$_javassist_0 class. This class is a proxy class dynamically generated by Hibernate using the Javassist project. When Hibernate delays loading the associated entity, Javassist will be used to generate a dynamic proxy object, and this proxy object will be responsible for proxying the "not loaded yet".
As long as the application needs to use an associated entity that is "not loaded yet", the Person_$$_javassist_0 proxy object will be responsible for loading the real associated entity and returning the actual associated entity - this is the most typical proxy pattern.
Click the person attribute in the Variables window shown in Figure 4 (that is, force the person attribute to use in debug mode), and then you will see the following SQL statements in the Console window of Eclipse:
select 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=?
The above SQL statement is a statement that captures the associated entity of "delay loading". At this time, you can see the results shown in Figure 5 of the Variables window output:
Figure 5. Loaded entity
Hibernate adopts the "delayed load" mode to manage associated entities. In fact, when loading the main entity, it does not really grab the corresponding data of the associated entity, but just dynamically generates an object as the proxy of the associated entity. When an application really needs to use an associated entity, the proxy object is responsible for grabbing records from the underlying database and initializing the real associated entity.
In Hibernate delay loading, what the client program starts to get is a dynamically generated proxy object, while the real entity is delegated to the proxy object for management - this is the typical proxy pattern.
Agent Mode
The proxy mode is a design mode with a very wide application. When the client code needs to call an object, the client actually does not care whether to get the object accurately. It only needs an object that can provide the function. At this time, we can return the proxy (Proxy) of the object.
In this design method, the system will provide an object with a proxy object, and the proxy object controls the reference to the source object. A proxy is a Java object that acts on behalf of another Java object. In some cases, the client code does not want or cannot directly call the callee, and the proxy object can act as an intermediary between the client and the target object.
For clients, it cannot distinguish the difference between a proxy object and a real object, nor does it need to distinguish the difference between a proxy object and a real object. The client code does not know the real proxy object. The client code is interface-oriented and it only holds an interface of the proxy object.
In short, as long as the client code cannot or does not want to directly access the called object - there are many reasons for this situation, such as creating an object with a high system overhead, or the called object is on a remote host, or the function of the target object is not enough to meet the needs..., but an additional proxy object is created to return it to the client for use, so this design method is the proxy mode.
The following demonstrates a simple proxy mode. The program first provides an Image interface, representing the interface implemented by a large image object. The interface code is as follows:
Listing 3. Image.java
public interface Image{void show();}This interface provides an implementation class that simulates a large image object, and the constructor of the implementation class uses the Thread.sleep() method to pause 3s. Below is the program code for the BigImage.
Listing 4. BigImage.java
// Use this BigImage to simulate a large image public class BigImage implements Image{public BigImage(){try{// Program pauses 3s mode simulation system overhead Thread.sleep(3000); System.out.println("Image loading successfully...");}catch (InterruptedException ex){ex.printStackTrace();}}// Implement the show() method in Image public void show(){System.out.println("Draw the actual large picture");}}}The above program code pauses 3s, which indicates that it takes 3s time overhead to create a BigImage object - the program uses this delay to simulate the system overhead caused by loading this image. If the proxy mode is not used, the system will generate a 3s delay when BigImage is created in the program. To avoid this delay, the program provides a proxy object for the BigImage object, and the proxy class of the BigImage class is as follows.
Listing 5. ImageProxy.java
public class ImageProxy implements Image{// Combine an image instance as the proxy object private Image image;// Use abstract entities to initialize the proxy object public ImageProxy(Image image){this.image = image;}/*** Rewrite the show() method of the Image interface* This method is used to control access to the proxy object, * and is responsible for creating and deleting the proxy object as needed*/public void show(){// Create the proxy object only if (image == null){ image = new BigImage();}image.show();}}The ImageProxy proxy class above implements the same show() method as BigImage, which allows the client code to use the proxy object as BigImage after obtaining the proxy object.
Control logic is added to the show() method of the ImageProxy class. This control logic is used to control that the proxy BigImage object will be created only when the system actually calls the show() of image. The following program needs to use the BigImage object, but the program does not directly return the BigImage instance, but first returns the BigImage proxy object, as shown in the following program.
Listing 6. BigImageTest.java
public class BigImageTest{public static void main(String[] args){long start = System.currentTimeMillis();// The program returns an Image object, which is just the proxy object of BigImage Image image = new ImageProxy(null);System.out.println("The time overhead of the system obtaining the Image object:" +(System.currentTimeMillis() - start));// The program will actually create the proxy object when the show() method of the image proxy is actually called. image.show();}}The above program initializes image very quickly because the program does not really create the BigImage object, but just gets the ImageProxy proxy object - until the program calls the image.show() method, the program needs to actually call the show() method of the BigImage object, and the program actually creates the BigImage object at this time. Run the above program and see the results shown in Figure 6.
Figure 6. Improve performance using proxy mode
Seeing the running results shown in Figure 6, readers should be able to agree that using proxy mode improves the system performance of obtaining Image objects. But some readers may ask questions: When a program calls the show() method of the ImageProxy object, it also needs to create a BigImage object, but the system overhead has not been really reduced? It's just that this system overhead is delayed?
We can answer this question from the following two perspectives:
Delaying the creation of BigImage until it is really needed can ensure the smooth operation of the previous program, and reduce the survival time of BigImage in memory, saving the system's memory overhead from a macro perspective.
In some cases, maybe the program will never actually call the show() method of the ImageProxy object - meaning that the system does not need to create a BigImage object at all. In this case, using the proxy mode can significantly improve system operation performance.
Totally similar, Hibernate also uses proxy mode to "delay" the time to load the associated entity. If the program does not need to access the associated entity, the program will not crawl the associated entity. This can save the system's memory overhead and shorten the time when Hibernate loads the entity.
summary
Hibernate lazy load is essentially an application of proxy mode. In the past years, we have often used proxy mode to reduce system memory overhead and improve application performance. Hibernate takes advantage of this advantage of proxy mode and combines Javassist or CGLIB to dynamically generate proxy objects, which adds flexibility to proxy mode. Hibernate gives this usage a new name: lazy loading. In any case, fully analyzing and understanding the implementation of these open source frameworks can better experience the advantages of classic design models.
I hope that the description in this article will be helpful to everyone's Java programming based on the Hibernate framework.