To avoid unnecessary performance overhead caused by association in some cases.
The so-called delayed loading means that data loading is performed only when data is needed.
You can use the load method to specify the proxy that can return the target object.
You can use the lazy attribute of the class to enable the delayed Loading Function of object objects. Ing file)
TUser user = (Tuser)session.load(TUser.class, new Integer(1)); (1)System.out.println(user.getName()); (2)
When the program runs to 1), Hibernate has retrieved the corresponding records from the database table and constructed a complete TUser object.
Modify the above ing Configuration:
Lazy = "true"
Check the status of the user object after the code is run to 1 ).(Eclipse Debug view)
We can see that the user object is not the same as the object class we previously defined. Its current type is described as TUser $ EnhancerByCGLIB $ bede8986 and its attributes are null.
While observing the screen log, there is no Hibernate SQL output at this time, which means that when we get the user object reference, Hibernate does not perform the database query operation.
Code to 2), observe the user object status again
The user object's name attribute is still null, but the screen output shows that the query operation has been executed, and the user. name attribute is also correctly output.
Why is the difference between the two query operations?
The reason is the proxy mechanism of Hibernate.
CGLib is introduced in Hibernate as the basis for proxy implementation. This is why we get an object of the TUser $ EnhancerByCGLIB $ bede8986 type.
CGLib can dynamically generate Java Class at runtime. The basic implementation principle of the proxy mechanism here is to construct a dynamic object containing all the attributes and methods of the target object by using CGLib, which is equivalent to a subclass of the Dynamic Construction of the target object) to return, it serves as an intermediary to provide more features for the target object.
From the memory view, the real tuserobject is in the cglib?callback_0.tar get attribute of the proxy class.
When we call the user. getName methodThe called object is cglib1_callback_0.getname()(. When this parameter is called, the system first checks whether the target object exists in cglib1_callback_0.tar get.
If yes, The getName method of the target object is called to return,If the target object is emptyThen, the initiator Data Warehouse query command creates the target object and sets it to cglib?callback_0.tar get.
In this way, an intermediate proxy is used to implement the data delay loading function. Hibernate performs database query only when the client program actually calls the Value Method of the object class.
2. Delayed loading of Collection types
In the delayed Loading Mechanism of Hibernate, the delayed loading feature of the set is of the greatest significance and is also an important part in practical application.
If we only want to get the age attribute of the user, but do not care about the address information of the user (the address is set type), the feature of automatically loading the address is especially redundant, this results in a great waste of performance.
Modify the lazy attribute in the previous one-to-multiple relationship to true, that is, the associated object is specified to use delayed loading:
Run the following code:
Criteria criteria = session. createCriteria (TUser. class); criteria. add (Expression. eq ("name", "Erica"); List userList = criteria. list (); Tuser user = (Tuser) userList. get (0); System. out. println ("User Name =>" + user. getName (); Set hset = user. getAddresses (); session. close (); // close sessionTaddress addr = (Taddress) hset. toArray () [0]; System. out. println (addr. getAddress ());
An exception is thrown during running:
LazyInitializationException-failed to lazily initialize a collection-no session or session was closed
If you make a slight adjustment and put session. close at the end of the Code, this will not happen.
This means that Hibernate tries to load the actual dataset from the database through the session only when we actually load the address associated with the user, because we have closed the session before reading the address, so the above error occurs.
There is a problem here. If we adopt the delayed loading mechanism, but want to implement the non-delayed Loading Function in some cases, that is to say, after the session is closed, the address attribute of the user is still allowed.
The Hibernate. initialize method forces Hibernate to load the associated object set immediately:
Hibernate. initialize (user. getAddress (); session. close (); // use Hibernate. the initialize method forcibly reads the data. // The addresses object can be detached from the session to perform the Set hset = user operation. getAddresses (); Taddress addr = (Taddress) hset. toArray () [0]; System. out. println (addr. getAddress ());
Hibernate has made a lot of efforts to achieve transparent latency loading. This includes the independent implementation of the jdk Collection interface.
If you try to use HashSet to forcibly convert the Set object returned by Hibernate:
Set hset = (HashSet) user. getAddresses ();
It will get a java at runtime. lang. classCastException. In fact, a specific Set of Hibernate is returned to implement "net. sf. hibernate. collection. set, rather than JDK Set implementation in the non-traditional sense.
This is exactly why JDK Collection interfaces such as Set and Map must be used when writing POJO, rather than JDK Collection implementation classes such as HashSet and HashMap) the reason for declaring Colleciotn attributes is private Set addresses, rather than private HashSet addresses ).
When session. save (user); is called, how does Hibernate handle its associated Addresses object set?
Assume that TUser is defined as follows:
public class TUser implements Serializable{ … private Set addresses = new HashSet(); …}
We declare an addresses attribute through the Set interface, and create a HashSet as the initial instance of addresses, so that after creating a TUser instance, we can add an associated address object for it:
TUser user = new TUser();TAddress addr = new TAddress();addr.setAddress(“HongKong”);user.getAddresses().add(addr);session.save(user);
In the Debug view of Eclipse, you can see the changes of the user object before and after the session. save method is executed:
First, due to the Insert operation, Hibernate obtains the id value generated by the database and fills it with the id attribute of the user object.
On the other hand, Hibernate uses its own Collection to implement "net. sf. hibernate. collection. set replaces the HashSet ses SSEs attribute in the user and fills it with data to ensure that the new addresses and the original addresses contain the same entity element.
Let's take a look at the following code:
TUser user = (TUser)session.load(TUser.class, new Integer(1));Collection addSet = user.getAddresses();(1)Iterator it = addSet.iterator();(2)while(it.hasNext()){ TAddress addr = (TAddress)it.next(); System.out.println(addr.getAddresses());}
When the code is executed to 1), the addresses dataset has not been read. The addrSet object we get is actually a net. sf. hibernate. collection. Set instance that does not contain any data.
Code to 2), the real data read operation starts.
Observe the net. sf. hibernate. collection. Set. iterator method to see:
public Iterator iterator(){ read(); return new IteratorProxy(set.iterator());}
Until now, the real data loading read method) does not start execution.
The read method first searches the cache for data indexes that meet the conditions.
HereNote:The concept of data index. When Hibernate caches the set type, it stores the data in two parts. The first is the id list of all objects in the Set, which is also called the data index, in this example, the Data Index contains the list of IDs of all address objects with userid = 1), followed by Entity objects.
If no corresponding data index is found, execute a Select SQL statement... From t_address where user_id = ?) Obtain all matching records, and then construct the object and Data Index to return. Object objects and data indexes are also included in the cache.
If the corresponding data index is found, the list of all IDS is retrieved from the data index, and the corresponding address object is queried from the cache according to the id list. If yes, return the data in the cache. If the data corresponding to the current id is not found, execute the corresponding Select SQL to obtain the corresponding address record. In this example, select... From t_address where user_id = ?).
This introduces another performance concern, that is, the Cache Policy of the associated object.
If we set a cache for a collection class, such:
<set name="addresses" table="t_address" lazy="true" inverse="true" cascade="all" sort="unsorted"> <cache usage="read-only"/> <key column="user_id"/> <one-to-many class="…TAddress"/></set>
Note that <cache usage = "read-only"/> only caches the data index. That is to say, the configuration here is actuallyOnly data indexes in the set are cached.Does not include all entity elements in this set.
Run the following code:
TUser user = (TUser) session. load (TUser. class, new Integer (1); Collection addSet = user. getAddresses (); // load the user for the first time. addressesIterator it = addSet. iterator (); while (it. hasNext () {TAddress addr = (TAddress) it. next (); System. out. println (addr. getAddresses ();} System. out. println ("\ n = Second Query = \ n"); TUser user2 = (TUser) session2.load (TUser. class, new Integer (1); Collection addSet2 = user2.getAddress (); // loads the user for the second time. addressesIterator it2 = addSet2.iterator (); while (it2.hasNext () {TAddress addr = (TAddress) it2.next (); System. out. println (addr. getAddress ());}
Observe the screen log output:
Hibernate: select tuser0 _. id as id3_0 _, tuser0 _. name as name3_0 _, tuser0 _. age as age3_0 _, tuser0 _. group_id as group4_3_0 _ from t_user3 tuser0 _ where tuser0 _. id =?
Hibernate: select addresses0 _. user_id as user7_1 _, addresses0 _. id as id1 _, addresses0 _. id as id7_0 _, addresses0 _. address as address7_0 _, addresses0_.zip code as zipcode7_0 _, addresses0 _. tel as tel7_0 _, addresses0 _. type as type7_0 _, addresses0 _. idx as idx7_0 _, addresses0 _. user_id as user7_7_0 _ from t_address addresses0 _ where addresses0 _. user_id =? Order by addresses0_.zip code asc
Hongkong
Hongkong
=== Second Query ===
Hibernate: select tuser0 _. id as id3_0 _, tuser0 _. name as name3_0 _, tuser0 _. age as age3_0 _, tuser0 _. group_id as group4_3_0 _ from t_user3 tuser0 _ where tuser0 _. id =?
Hibernate: select taddress0 _. id as id7_0 _, taddress0 _. address as address7_0 _, taddress0_.zip code as zipcode7_0 _, taddress0 _. tel as tel7_0 _, taddress0 _. type as type7_0 _, taddress0 _. idx as idx7_0 _, taddress0 _. user_id as user7_7_0 _ from t_address taddress0 _ where taddress0 _. id =?
Hibernate: select taddress0 _. id as id7_0 _, taddress0 _. address as address7_0 _, taddress0_.zip code as zipcode7_0 _, taddress0 _. tel as tel7_0 _, taddress0 _. type as type7_0 _, taddress0 _. idx as idx7_0 _, taddress0 _. user_id as user7_7_0 _ from t_address taddress0 _ where taddress0 _. id =?
Hongkong
Hongkong
As you can see, when you obtain the associated ses SSEs set for the second time, the Select SQL statement is executed twice.
It is precisely because <set…> <Cache usage = "read-only"/>... </Set>. After the addresses set is loaded for the first time, the data index is cached.
When the addresses set is loaded for the second time, Hibernate finds the data index in the cache, so it retrieves all the current IDS from the index. At this time, the database has three matching records, therefore, a total of three IDs are obtained. Then, the corresponding object is searched in the cache based on the three IDs but not found. Therefore, a database query is initiated, the Select SQL reads records from the t_address table by id.
Because of the existence of data indexes in the cache, it seems that the number of SQL statements is more, which leads to the second data query with greater performance overhead than the first time.
What causes this problem?
This is because we only configure the cache for the collection type, so that Hibernate will only cache data indexes, and will not include the entity elements in the collection into the cache at the same time.
We must also specify a cache policy for the object in the collection type, such:
At this time, Hibernate will cache the objects in the set.
Run the above Code again:
The two outputs seem the same. Where is the problem ?)
Net. sf. hibernate. collection. set. iterate method. Similarly, observe the net. sf. hibernate. collection. set. size/isEmpty method or other hibernate. the same method implementation in collection, we can see the same processing method.
This is also the principle of using a custom Collection type to implement delayed data loading.
In this way, through its own Collection implementation, Hibernate can easily implement the delayed loading feature at the Collection layer. The underlying database operations are stimulated only when the program actually reads the content of the Collection, which provides more flexible adjustment measures for system performance.
3. Delayed attribute Loading
Assume that the t_user table contains a Resume field of the long text type, which stores the Resume data of the user. Reading long text fields will bring about a large performance overhead. Therefore, we decided to set it to delayed loading. Only when we really need to process the resume information, read from the database table.
First, modify the resing configuration file and set the lazy attribute of the Resume field to true:
Unlike the delayed loading of objects and set types, the delayed Loading Mechanism of Hibernate3 attributes must be used to enhance the buildtime bytecode instrumentation of binary Class files ).
Here, we use Ant to call the Hibernate class booster to harden the TUser. class file. The Ant script is as follows:
<project name="HibernateSample" default="instrument” basedir="."> <property name="lib.dir" value="./lib" /> <property name="classes.dir" value="./bin" /> <path id="lib.class.path"> <fileset dir="${lib.dir}"> <include name="**/*.jar" /> </fileset> </path> <target name="instrument"> <taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask"> <classpath path="${classes.dir}" /> <classpath refid="lib.class.path" /> </taskdef> <instrument verbose="true"> <fileset dir="${classes.dir}/com/redsaga/hibernate/db/entity" > <include name="TUser.class" /> </fileset> </instrument> </target></project>
Note the configuration of each path when using this script. In this example, the script is located in the root directory of the Eclipse project ,. /bin is the default compiling output path of Eclipse ,. /bin stores the required jar file hibernate3.jar and the class library required by Hibernate ).
The above Ant script will harden the TUser. class file. If you decompile it, you can see the following content:
Package com. redsaga. hibernate. db. entity; import java. io. serializable; import java. util. set; import net. sf. cglib. transform. impl. interceptFieldCallback; import net. sf. cglib. transform. impl. interceptFieldEnabled; public class TUserimplements Serializable, InterceptFieldEnabled {public InterceptFieldCallback getInterceptFieldCallback () {return $ CGLIB_READ_WRITE_CALLBACK;} public InterceptFieldCallback setI NterceptFieldCallback (InterceptFieldCallback interceptFieldcallback) {$ CGLIB_READ_WRITE_CALLBACK = interceptFieldcallback ;}... ... Public String $ cglib_read_resume () {resume; if ($ CGLIB_READ_WRITE_CALLBACK! = Null) goto _ L2; else goto _ L1; _ L1: return; _ L2: String s; return (String) $ CGLIB_READ_WRITE_CALLBACK.readObject (this, "resume ", s);} public void $ cglib_write_resume (String s) {resume = $ CGLIB_READ_WRITE_CALLBACK = null? S :( String) $ CGLIB_READ_WRITE_CALLBACK.writeObject (this, "resume", resume, s );}... ...}
As you can see, the content of the TUser class has changed a lot. In the meantime, cglib-related code is implanted in large numbers. Through this code, methods of the TUser class can be intercepted during Hibernate runtime, thus providing the technical basis for implementation of the delayed loading mechanism.
After the above processing, run the following test code:
String hql = “from TUser user where user.name=’Erica’”;Query query = session.createQuery(hql);List list = query.list();Iterator it = list.iterator();while(it.hasNext()){ TUser user = (TUser)it.next(); System.out.println(user.getName()); System.out.println(user.getResume());}
Observe the output log:
We can see that during this process, Hibernate successively executed two SQL statements. The first sentence is used to read non-delayed loading fields in the TUser class. Then, when the user. getResume () method is called, the second SQL statement is called to read the Resume field data from the database table. Delayed loading of attributes has been implemented.