简体   繁体   中英

In Hibernate Component tag lazy loading is not working

I have a small hibernate application as above: AAAA

BankAccount class is as follows:

    package in.co.way2learn;
    import java.util.Set;
    public class BankAccount {
         private int accountNumber;
         private String accountHoldersName;
         private int balance;
         private Address address;
         private Set<String> emails;
         //setters and getters
    }

Address class is as below:

package in.co.way2learn;

public class Address {
    private String addressLine1;
    private String addressLine2;
    private String city;
    private String country;
    private int pinCode;

    //setters and getters
}

BankAccount.hbm.xml file is as below:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Jul 2, 2014 3:59:34 PM by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping package="in.co.way2learn">
    <class name="BankAccount">
        <id name="accountNumber" type="integer">
            <generator class="assigned"/>
        </id>
        <property name="accountHoldersName" type="string"/>
        <property name="balance" type="integer"/>
        <component name="address" class="Address" lazy="true"> 
            <property name="addressLine1"/>
            <property name="addressLine2"/>
            <property name="city"/>
            <property name="country"/>
            <property name="pinCode"/>
        </component>
        <set name="emails" order-by="email asc" table="bankaccount_emails">
            <key column="SNo"/>
            <element column="email" type="string"/>
        </set>
    </class>
</hibernate-mapping>

hibernate.cfg.xml file is as below:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">
            org.gjt.mm.mysql.Driver
        </property>
        <property name="hibernate.connection.password">root</property>
        <property name="hibernate.connection.url">
            jdbc:mysql://localhost:3306/way2learnDB
        </property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.dialect">
            org.hibernate.dialect.MySQLInnoDBDialect
        </property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.hbm2ddl.auto">update</property>

        <mapping resource="in/co/way2learn/BankAccount.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

Now my question is in BankAccount.hbm.xml file in the component tag I am using using lazy="true", when ever I am firing select query on BankAccount class using session.get(BankAccount.class, 1235); It is loading address details also from database, the code I used to fire select query is below:

Session session=sessionFactory.openSession();
Transaction transaction=session.beginTransaction();
BankAccount bankAccount=(BankAccount)session.get(BankAccount.class, 1235);
transaction.commit();
session.close();

The query fired is

Hibernate: 
    select
        bankaccoun0_.accountNumber as accountN1_0_0_,
        bankaccoun0_.accountHoldersName as accountH2_0_0_,
        bankaccoun0_.balance as balance3_0_0_,
        bankaccoun0_.addressLine1 as addressL4_0_0_,
        bankaccoun0_.addressLine2 as addressL5_0_0_,
        bankaccoun0_.city as city6_0_0_,
        bankaccoun0_.country as country7_0_0_,
        bankaccoun0_.pinCode as pinCode8_0_0_ 
    from
        BankAccount bankaccoun0_ 
    where
        bankaccoun0_.accountNumber=?

But I am expecting address details will be loaded lazily from database when ever I used bankAccount.getAddress() method only?

Now can any one please explain why hibernate is loading address details eagerly, and how to load then lazily?

Take an example from below code:-

class B {  
    private C cee;  

    public C getCee() {  
        return cee;  
    }  

    public void setCee(C cee) {  
        this.cee = cee;  
    }  
}  

class C {  
    // Not important really  
}  

Right after loading B, you may call getCee() to obtain C. But look, getCee() is a method of your class and Hibernate has no control over it. Hibernate does not know when someone is going to call getCee() . That means Hibernate must put an appropriate value into " cee " property at the moment it loads B from database.

If proxy is enabled for C, Hibernate can put a C-proxy object which is not loaded yet, but will be loaded when someone uses it. This gives lazy loading for one-to-one.

But now imagine your B object may or may not have associated C (constrained="false") . What should getCee() return when specific B does not have C? Null. But remember, Hibernate must set correct value of "cee" at the moment it set B (because it does no know when someone will call getCee() ). Proxy does not help here because proxy itself in already non-null object.

If your B->C mapping is mandatory (constrained=true) , Hibernate will use proxy for C resulting in lazy initialization. But if you allow B without C, Hibernate just HAS TO check presence of C at the moment it loads B. But a SELECT to check presence is just inefficient because the same SELECT may not just check presence, but load entire object. So lazy loading goes away.

Workaround1 : - Just add annotation or entry in hdm file for @JoinColumn for reference private Address address; .

Workaround2 :- add optional=false in OneToOne relationship

Other solutions for this problem:

The simplest one is to fake one-to-many relationship. This will work because lazy loading of collection is much easier then lazy loading of single nullable property but generally this solution is very inconvenient if you use complex JPQL/HQL queries.

The other one is to use build time bytecode instrumentation. For more details please read Hibernate documentation: 19.1.7. Using lazy property fetching. Remember that in this case you have to add @LazyToOne(LazyToOneOption.NO_PROXY) annotation to one-to-one relationship to make it lazy. Setting fetch to LAZY is not enough.

The last solution is to use runtime bytecode instrumentation but it will work only for those who use Hibernate as JPA provider in full-blown Java EE environment (in such case setting " hibernate.ejb.use_class_enhancer " to true should do the trick: Entity Manager Configuration) or use Hibernate with Spring configured to do runtime weaving (this might be hard to achieve on some older application servers). In this case @LazyToOne(LazyToOneOption.NO_PROXY) annotation is also required.

This will work for you.

Hibernate does not create proxies for components, that's why lazy loading does not work for them.

Solutions:

  1. Use bytecode instrumentation to enable lazy loading of non-entity fields. It has its own pitfalls and is not widely adopted.
  2. Use two different classes for BankAccount , one containing the Address component (as it is now), and one without it, and map them to the same table. Then, use the one without address in contexts in which you don't need addresses.
  3. Use fake one-to-one association between BankAccount and Address by making Address component an entity and mapping it to the same table. The drawback here is that you must not insert the Address instances (because you'll end up trying to insert a separate row in the table), but rather you have to read and update it after you insert the corresponding BankAccount entity instance.
  4. Change the db schema and move the component to its own separate table. Then simply promote the component to an entity and map it to the new table.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM