How to lazy load a one-to-one composition via hql

2020-07-27 05:11发布

问题:

If have an entity A with a bidirectional one-or-zero-to-one mapping with entity B.

The mapping is as follows:

<class name="EntityA" table="TABLE_A" mutable="true" lazy="true">
    <id name="idA" type="long" column="pk_a" unsaved-value="null">
        <generator class="sequence">
            <param name="sequence">pk_a_seq</param>
        </generator>
    </id>
    <one-to-one name="propertyB" class="EntityB" property-ref="propertyA" constrained="true" outer-join="false"/>
</class>

and

<class name="EntityB" table="TABLE_B" mutable="true" lazy="true">
    <id name="idB" type="long" column="pk_b" unsaved-value="null">
        <generator class="sequence">
            <param name="sequence">pk_b_seq</param>
        </generator>
    </id>
    <many-to-one name="propertyA" class="EntityA" not-null="true" unique="true" lazy="proxy" column="fk_a"/>
</class>

When I do an hql query (or rather, a named hql query) for EntityA, hibernate eagerly loads EntityA#propertyB with a separate select statement.

My problem with that is if my hql returns 1000 EntityA's (with all having their own respective EntityB's), hibernate will do n+1 queries (1st query would be for EntityA returning 1000 results, while the n queries would be coming from the EntityA#propertyB select lazy loading).

However, I do not need those EntityA#propertyB's that's why I want to lazy load them instead (without having hibernate use a separate sql query).

Is that possible? And if it is, how do I do that?

Thanks, Franz

回答1:

I've fixed this problem.

What I did was to create turn the field EntityA#propertyB into a Set with the name EntityA#propertyBs. But I retained the EntityA#getPropertyB() and EntityA#setPropertyB(EntityB propertyB) accessor methods.

The method bodies of those accessor methods are now something like this:

public EntityB getPropertyB() {
    return CollectionUtils.get(propertyBs, 0);
}

public void setPropertyBs(EntityB propertyB) {
    propertyBs= Collections.singleton(propertyB);
}

Then in my mapping, I mapped the set EntityA#propertyBs and specify the access to 'field'.

<set name="scheduledAdInfos" lazy="true" fetch="subselect" access="field" cascade="none" inverse="true">
    <key column="pk_a"/>
    <one-to-many class="EntityB"/>
</set>

With this setup, you can now create a lazy mapping from the owning POJO (EntityA) to the owned POJO (EntityB) even if TABLE_A is owned by TABLE_B.



回答2:

Short answer: No you cannot do that, at least not without changing the database and the mapping. You basically have to reverse the one-to-one mapping and the foreign key relation to work in the way you want.


Longer answer: Hibernate can lazy load associations. The way it does this is by injecting a proxy object that holds the ID of the referenced object.

In your case, the mapping is such that the foreign-key column is in TABLE_B, that is, where you use the many-to-one mapping. So if you load a B, hibernate finds the FK reference in the fk_a column and can create a proxy that holds this value. When the proxy is accessed the corresponding entity is loaded.

What if a record from table A is selected? Hibenate will create an A object, but to be able to fill the propertyB, it will have to look into the TABLE_B, to find the corresponding row with fk_a=a.id. There is no other way for Hibernate to find out what record to load at lazy loading time.

Actually, this would be an improvement for Hibernate, since it should also be able to do the loading on other unique keys while lazy loading, but the current implementation does not allow this, maybe you can raise an issue.