Search This Blog

Saturday, 7 April 2012

One to One and Proxies

As a part of its default fetch strategy, Hibernate uses proxies for collections and all associations when we get an Entity from the database. The proxies have only their id set with access to any other property triggering a further select statement. The idea here is to only hit these tables when the data is needed. But the behavior varies slightly for one to one associations.
If the one to one is association is an optional one then the proxy feature will not be used. Consider the following example classes that have a one to one association between them
public class Entity {
    private Integer id;   
    private String name;
    private Association association;
        //setters and getters
}
public class Association {
    private Integer referencedId;
    private Entity owningEntity;
    private String name;
        //setters and getters
}
Let us implement a shared primary key association here.
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.data.proxy">
    <class name="Entity" table="OWNING_ENTITY">

        <id name="id" type="integer" column="ID">
            <generator class="native" />
        </id>
        <one-to-one name="association" class="Association" cascade="all" />
        <property name="name" type="string">
            <column name="NAME" length="50" not-null="true" />
        </property>
    </class>
</hibernate-mapping>

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.data.proxy">
    <class name="Association" table="ASSOC">
        <id name="referencedId" type="integer">
            <column name="referenced_Id" />
            <generator class="foreign">
                <param name="property">owningEntity</param>
            </generator>
        </id>
        <one-to-one name="owningEntity" class="Entity" 
            constrained="true" outer-join="false"/>
        <property name="name" type="string">
            <column name="NAME" length="50" not-null="true" />
        </property>
    </class>
</hibernate-mapping>
If we were to load an entity object
public static void testLoad() {
    Session session = sessionFactory.openSession();
    Entity entity = (Entity) session.load(Entity.class, id);
    System.out.println("Entity class is " + entity.getClass() 
            + " and association class is " + entity.getAssociation().getClass());
    session.close();
}
The logs would indicate the below:
    select
        entity0_.ID as ID0_1_,
        entity0_.NAME as NAME0_1_,
        associatio1_.referenced_Id as referenced1_1_0_,
        associatio1_.NAME as NAME1_0_ 
    from
        OWNING_ENTITY entity0_ 
    left outer join
        ASSOC associatio1_ 
            on entity0_.ID=associatio1_.referenced_Id 
    where
        entity0_.ID=?
Entity class is class com.data.proxy.Entity$$EnhancerByCGLIB$$dfcc5dd8 and assoc
iation class is class com.data.proxy.Association
As can be seen,along with the Entity class, the related association was also fetched from the database. No proxy was used here.
The reason is that for the Entity class, the association is optional. We can always create the Entity object with a null association and save it. Hibernate when it is loading the Entity object is not aware if the association is to be treated as null, or represented with a proxy. As a result it needs to query the ASSOC table to detect if the record exists.As it is already querying the table, Hibernate also fetches the data (rather than perform an additional select). Hence the outer-join here
The scenario changes when we access the relation from the other side.
public static void testLoadViaAssoc() {
    Session session = sessionFactory.openSession();
    Association association = (Association) session.load(Association.class, id);
    System.out.println("Entity class is " + association.getClass() 
            + " and association class is " + association.getOwningEntity().getClass());
    session.close();
}
The logs indicate the following:
    select
        associatio0_.referenced_Id as referenced1_1_0_,
        associatio0_.NAME as NAME1_0_ 
    from
        ASSOC associatio0_ 
    where
        associatio0_.referenced_Id=?
Entity class is class com.data.proxy.Association$$EnhancerByCGLIB$$4936ac68 and 
association class is class com.data.proxy.Entity$$EnhancerByCGLIB$$dfcc5dd8
As can be seen, the entity is not fetched by a join and is represented by a proxy.
The reason here is the use of "constrained = true" attribute. The true value tells Hibernate that it is an absolute must for an association to have an entity. Thus Hibernate can very confidently go ahead and represent the Entity using a proxy.
Similar is the case if we use a foreign key relation here. The modified hbms would be 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">
<hibernate-mapping package="com.data.proxy">
    <class name="Entity" table="OWNING_ENTITY">

        <id name="id" type="integer" column="ID">
            <generator class="native" />
        </id>
        <many-to-one name="association"  foreign-key="OWNING_ENTITY_FK1" 
            class ="Association"
            cascade="all" unique="true" >
            <column name="bill_detail_id" ></column>
        </many-to-one>
        <property name="name" type="string">
            <column name="NAME" length="50" not-null="true" />
        </property>
    </class>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.data.proxy">
    <class name="Association" table="ASSOC">
        <id name="referencedId" type="integer">
            <column name="referenced_Id" />
            <generator class="identity">
            </generator>
        </id>
        <one-to-one name="owningEntity" class="Entity"
            property-ref="association" />
        <property name="name" type="string">
            <column name="NAME" length="50" not-null="true" />
        </property>
    </class>
</hibernate-mapping>
In this case the Entity object has the association as a foreign key column in the ENTITY row itself. So hibernate is capable of using the proxy for the association.On executing the above testLoad() method:
    select
        entity0_.ID as ID0_0_,
        entity0_.bill_detail_id as bill2_0_0_,
        entity0_.NAME as NAME0_0_ 
    from
        OWNING_ENTITY entity0_ 
    where
        entity0_.ID=?
Entity class is class com.data.proxy.Entity$$EnhancerByCGLIB$$dfcc5dd8 and assoc
iation class is class com.data.proxy.Association$$EnhancerByCGLIB$$4936ac68
On executing the testLoadViaAssoc method (to navigate the relation from the association end) the logs are as follows:
    select
        associatio0_.referenced_Id as referenced1_1_1_,
        associatio0_.NAME as NAME1_1_,
        entity1_.ID as ID0_0_,
        entity1_.bill_detail_id as bill2_0_0_,
        entity1_.NAME as NAME0_0_ 
    from
        ASSOC associatio0_ 
    left outer join
        OWNING_ENTITY entity1_ 
            on associatio0_.referenced_Id=entity1_.bill_detail_id 
    where
        associatio0_.referenced_Id=?
Entity class is class com.data.proxy.Association$$EnhancerByCGLIB$$4936ac68 and 
association class is class com.data.proxy.Entity
Again this makes sense, as Hibernate would not have any information of the Entity record in the ASSOC table. Hence the join fetch. I tried to force Hibernate to use a proxy here, by modifying the association in the Association.hbm.xml
<one-to-one name="owningEntity" class="Entity" constrained ="true"
    property-ref="association" />
On executing testLoadViaAssoc method again:
    select
        associatio0_.referenced_Id as referenced1_1_0_,
        associatio0_.NAME as NAME1_0_ 
    from
        ASSOC associatio0_ 
    where
        associatio0_.referenced_Id=?
    select
        entity0_.ID as ID0_0_,
        entity0_.bill_detail_id as bill2_0_0_,
        entity0_.NAME as NAME0_0_ 
    from
        OWNING_ENTITY entity0_ 
    where
        entity0_.bill_detail_id=?
Entity class is class com.data.proxy.Association$$EnhancerByCGLIB$$4936ac68 and 
association class is class com.data.proxy.Entity
Here Hibernate did not apply the outer join, but when I called the getClass method it executed a second fetch and did not use the Proxy.
  1. In conclusion, we can say that Hibernate uses the proxy as long as it is assured that an association exists. 
  2. If it needs to execute an additional query to verify the existence of a relation, than it will also fetch the data from the table and thus skip the proxy altogether.
If you still want a the association to be a proxy than you can try this

No comments:

Post a Comment