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
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.
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:
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.AssociationAs 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$$dfcc5dd8As 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$$4936ac68On 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.EntityAgain 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.EntityHere 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.
- In conclusion, we can say that Hibernate uses the proxy as long as it is assured that an association exists.
- 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.
No comments:
Post a Comment