【问题标题】:How Lazy loading of child records works in Hibernate?在 Hibernate 中延迟加载子记录是如何工作的?
【发布时间】:2014-03-11 13:50:46
【问题描述】:

我知道上面的问题很常见,但我只是想知道 Hibernate 何时以及如何获取延迟加载的子记录。

下面是示例表结构:

表结构:

employee_table(e_id, e_name, e_sal)
(100, XYZ, 20000)

mobile_table(m_id, m_number, e_id)
(1, 8728271817, 100)
(2, 0983813919, 100)

Employee.java

public class Employee implements Serializable {

    private static final long serialVersionUID = 1930751473454928876L;

    private long employeeId;
    private String employeeName;
    private double employeeSal;

    private Set<Mobile> mobiles;

    public long getEmployeeId() {
        return employeeId;
    }
    public void setEmployeeId(long employeeId) {
        this.employeeId = employeeId;
    }
    public String getEmployeeName() {
        return employeeName;
    }
    public void setEmployeeName(String employeeName) {
        this.employeeName = employeeName;
    }
    public double getEmployeeSal() {
        return employeeSal;
    }
    public void setEmployeeSal(double employeeSal) {
        this.employeeSal = employeeSal;
    }
    public Set<Mobile> getMobiles() {
        return mobiles;
    }
    public void setMobiles(Set<Mobile> mobiles) {
        this.mobiles = mobiles;
    }
}

员工.hbm.xml

<hibernate-mapping>
    <class name="edu.sandip.hibernate.Employee" table="EMPLOYEE_T">
        <id name="employeeId" column="e_id" type="long">
            <generator class="increment" />
        </id>
        <property name="employeeName" column="e_name" type="string" />
        <property name="employeeSal" column="e_sal" type="double" />

        <set name="mobiles" table="MOBILE_T" inverse="true" lazy="true" fetch="select" >
            <key>
                <column name="e_id" not-null="true" />
            </key>
            <one-to-many class="edu.sandip.hibernate.Mobile" />
        </set>
    </class>
</hibernate-mapping>

Mobile.java

public class Mobile implements Serializable {

    private static final long serialVersionUID = 6279006639448045512L;

    private long mobId;
    private String mobileNumber;

    private Employee employee;

    public long getMobId() {
        return mobId;
    }
    public void setMobId(long mobId) {
        this.mobId = mobId;
    }
    public String getMobileNumber() {
        return mobileNumber;
    }
    public void setMobileNumber(String mobileNumber) {
        this.mobileNumber = mobileNumber;
    }
    public Employee getEmployee() {
        return employee;
    }
    public void setEmployee(Employee employee) {
        this.employee = employee;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((mobileNumber == null) ? 0 : mobileNumber.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Mobile other = (Mobile) obj;
        if (mobileNumber == null) {
            if (other.mobileNumber != null)
                return false;
        } else if (!mobileNumber.equals(other.mobileNumber))
            return false;
        return true;
    }
}

Mobile.hbm.xml

<hibernate-mapping>
    <class name="edu.sandip.hibernate.Mobile" table="MOBILE_T">
        <id name="mobId" column="m_id" type="long">
            <generator class="increment" />
        </id>
        <property name="mobileNumber" column="m_number" type="string" />
        <many-to-one name="employee" class="edu.sandip.hibernate.Employee" fetch="select">
            <column name="e_id" not-null="true" />
        </many-to-one>
    </class>
</hibernate-mapping>

下面是获取员工列表的代码块:

public List<Employee> getAllEmployees() {
    List<Employee> list = null;
    Session session = null;
    try {
        session = getSession();
        Query query = session.createQuery("FROM Employee");
        list = query.list();
        for(Employee employee : list) {
            System.out.println("Emaployee Name: " + employee.getEmployeeName);
            Set<Mobile> mobileSet = employee.getMobiles();
            System.out.println("Mobile Numbers: ");
            for(Mobile mobile : mobileSet) {
                System.out.println(mobile.getMobileNumber());
            }
        }
    } catch(Exception e) {
        e.printStackTrace();
    } finally {
        if(session != null) {
            System.out.println("session is still alive");
            releaseSession(session);
        }
    }
    return (list);
}

现在,这里的手机号码是 Employee 的子记录,它是由 Hibernate 根据 Employee.hbm.xml 中的 hibernate 配置延迟加载的(lazy = true)

在上面的代码中,打印员工姓名后,我正在打印该员工的手机号码 员工通过迭代 Set mobiles。

我查了一下,发现mobileSet的迭代实际上是在获取mobileNumbers。 即

for(Mobile mobile : mobileSet) {
    System.out.println(mobile.getMobileNumber());
}

所以,这是怎么回事?因为我没有使用任何休眠特定 API 来获取延迟加载的子记录。只是迭代一组手机号码。

那么 Hibernate 是如何在内部获取子记录的呢?当我在 mobileSet 上进行迭代时,Hibernate 正在做的后台工作是什么?

请帮助理解我的疑问。

【问题讨论】:

  • 如果您想了解它在访问内部对象时触发的查询,请在属性中标记 showSql = true 并进行调试

标签: java hibernate


【解决方案1】:

在休眠中,只要访问延迟加载的属性,就会触发延迟加载,并且它仍然由 em 管理。如果您在实体与实体管理器分离时访问延迟加载的属性,您将获得 org.hibernate.LazyInitializationException

要将逻辑应用于您的示例: mobile.getMobileNumber() 将触发代码中的延迟加载。 Hibernate 然后会自动向数据库发出查询。这是因为 hibernate 为延迟加载的属性实例化代理对象,一旦你尝试访问这些代理,hibernate 就会尝试从数据库中加载它们。如果对象被管理,这将起作用,如果实体被分离,这将导致上述异常。根本不需要使用休眠 API 来触发延迟加载,它只是在访问延迟配置的属性时自动发生

【讨论】:

  • 理论上我理解你的意思。但我知道“只要访问延迟加载的属性,就会触发休眠延迟加载”。但我想知道休眠是如何做到这一点的?如上面获取员工的示例,我正在使用 Query query = session.createQuery("FROM Employee"); list = query.list();但是为了检索子记录,我没有明确使用任何此类休眠特定 API。然后我的代码的哪一部分触发了延迟加载的子记录的获取,即一组mobileNumbers。你能解释一下吗?
  • 我已经编辑了我的回复以将解释应用于您的示例
【解决方案2】:

在您的 Employee.hbm.xml 文件中,您已声明 Mobile 的获取策略是惰性的。 这告诉 hibernate 为对应于 Employee 的表生成一个简单的 SQL 语句,而无需与相应的 Mobile 表执行任何连接。 但是,当调用 employee.getMobiles() 时, Hibernate 将使用带有前一张表主键的 where 子句对后一张表发出选择查询。

这里实现获取的方式导致了臭名昭著的N+1 problem

【讨论】:

  • first:如果你说'当调用employee.getMobiles() 时,Hibernate 将使用带有前一张表主键的where 子句对后一张表发出选择查询'。 Hibernate 如何发出 select 查询,因为我没有使用 hibernate 特定的 API 调用,它会告诉 hibernate 再发出一个 sql 查询。第二:所以如果我只是调用 'employee.getMobiles()' 并且不遍历 Set mobileSet 并从上述方法返回 List 列表:getAllEmployees(),那么在这个方法之外我能检索子记录,每个员工的mobileNumbers?
  • 当实体中存在延迟加载的对象时,Hibernate 使用代理(使用 Javassist 创建)代替延迟加载的对象。当该对象被检索(在此示例中通过 getMobiles() )并且该对象仍处于托管状态时,代理会触发对数据库的新选择查询。如果对象没有被管理,那么你会得到可怕的 LazyInitializationException。我建议您阅读 Java Persistence with Hibernate 了解详细信息
【解决方案3】:

本主题可以帮助您了解延迟加载背后的技巧:

How proxy loads the lazy property in Hibernate/JPA

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-30
    • 2011-01-12
    • 2013-10-03
    • 1970-01-01
    • 1970-01-01
    • 2012-02-04
    相关资源
    最近更新 更多