-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 8 posts ] 
Author Message
 Post subject: One-to-one property always selected with separate SQL
PostPosted: Wed Dec 28, 2005 3:25 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
I have many entities that store BLOB values in separate tables, joined to the entity's main table as a one-to-one relationship. The whole point of doing this is so that the BLOB doesn't get selected unless you really want it.

Yet, when I run an HQL query that selects entities with one-to-one properties to entities holding the BLOB values, NHibernate executes a separate SQL statement selecting from the BLOB table for every row returned by the main query.

Here is an example an entity mapping that references a BLOB entity:

Code:
<hibernate-mapping
   xmlns="urn:nhibernate-mapping-2.0"
   default-cascade="none"
   default-access="field.camelcase-underscore"
   assembly="Domain"
   namespace="MyCompany.Entities"
>
   <class name="Activity" table="oncd_activity" lazy="true"   dynamic-update="true" select-before-update="true">

      <id name="Id" column="activity_id" type="String" length="10" unsaved-value="null" access="field.camelcase-underscore">
         <generator class="MyCompany.Entities.DataAccess.NHibernate.IdentifierGenerator, EntityAccess"/>
      </id>
      ...
      <one-to-one name="Note" class="ActivityNote" cascade="delete" constrained="false" access="field.camelcase-underscore"/>
      ...
   </class>
</hibernate-mapping>


Here is the BLOB entity mapping:

Code:
<hibernate-mapping
   xmlns="urn:nhibernate-mapping-2.0"
   default-cascade="none"
   default-access="field.camelcase-underscore"
   assembly="Domain"
   namespace="MyCompany.Entities"
>
   <class name="ActivityNote" table="oncd_activity_note" lazy="true"   dynamic-update="true" select-before-update="true">

      <id name="Id" column="activity_id" type="String" length="10" unsaved-value="null" access="field.camelcase-underscore">
         <generator class="MyCompany.Entities.DataAccess.NHibernate.IdentifierGenerator, EntityAccess"/>
      </id>
      <property name="Note" column="note" type="StringClob" length="1073741823"  access="field.camelcase-underscore"/>
      <one-to-one name="Activity" class="Activity" cascade="delete" constrained="true" access="field.camelcase-underscore"/>
      ...
   </class>
</hibernate-mapping>


Here is my HQL query:

Code:
FROM      Activity                AS Activity
JOIN      Activity.Action         AS Action
JOIN      Activity.Result         AS Result
LEFT JOIN Activity.Users          AS ActivityUser
JOIN      ActivityUser.User       AS PrimaryUser
LEFT JOIN Activity.Contacts       AS ActivityContact
JOIN      ActivityContact.Contact AS PrimaryContact
WHERE     (   ActivityUser.IsPrimary    = true
           OR ActivityUser.IsPrimary    IS NULL)
AND       (   ActivityContact.IsPrimary = true
           OR ActivityContact.IsPrimary IS NULL)
AND       Action.Description LIKE :actionDescription
ORDER BY  Activity.DueDate DESC


And here is the log (my comments starting with ***)

Code:
***
*** This first select is expected, and just fine
***
2005-12-28 11:55:07,934 [4868] DEBUG NHibernate.SQL - select activity0_.activity_id as activity1_0_, setupactio1_.action_code as action_c1_1_, setupresul2_.result_code as result_c1_2_, users3_.activity_user_id as activity1_3_, setupuser4_.user_code as user_code4_, contacts5_.activity_contact_id as activity1_5_, contact6_.contact_id as contact_id6_, activity0_.completed_by_user_code as complet25_0_, activity0_.confirmed_time_to as confirm14_0_, activity0_.description as descript5_0_, activity0_.updated_date as updated_9_0_, activity0_.batch_address_type_code as batch_a18_0_, activity0_.confirmed_time as confirm12_0_, activity0_.source_code as source_24_0_, activity0_.duration as duration0_, activity0_.due_date as due_date0_, activity0_.created_by_user_code as created26_0_, activity0_.creation_date as creation6_0_, activity0_.batch_status_code as batch_s19_0_, activity0_.document_id as documen15_0_, activity0_.start_time as start_time0_, activity0_.completion_time as completi8_0_, activity0_.completion_date as completi7_0_, activity0_.project_code as project21_0_, activity0_.batch_result_code as batch_r22_0_, activity0_.result_code as result_23_0_, activity0_.opportunity_id as opportu29_0_, activity0_.action_code as action_17_0_, activity0_.confirmed_time_from as confirm13_0_, activity0_.campaign_code as campaig20_0_, activity0_.recur_id as recur_id0_, activity0_.notify_when_completed as notify_11_0_, activity0_.updated_by_user_code as updated27_0_, activity0_.milestone_activity_id as milesto16_0_, activity0_.priority as priority0_, activity0_.incident_id as inciden28_0_, setupactio1_.prompt_for_schedule as prompt_f5_1_, setupactio1_.source_code as source_c9_1_, setupactio1_.available_to_outlook as availabl6_1_, setupactio1_.campaign_code as campaig10_1_, setupactio1_.description as descript2_1_, setupactio1_.prompt_for_next as prompt_f8_1_, setupactio1_.chain_id as chain_id1_, setupactio1_.active as active1_, setupactio1_.available_to_mobile as availabl7_1_, setupactio1_.schedule_type as schedule4_1_, setupactio1_.sort_order as sort_order1_, setupactio1_.action_type_code as action_13_1_, setupresul2_.active as active2_, setupresul2_.description as descript2_2_, users3_.creation_date as creation5_3_, users3_.assignment_date as assignme2_3_, users3_.activity_id as activit11_3_, users3_.sort_order as sort_order3_, users3_.updated_date as updated_6_3_, users3_.primary_flag as primary_7_3_, users3_.user_code as user_code3_, users3_.attendance as attendance3_, users3_.created_by_user_code as created_8_3_, users3_.updated_by_user_code as updated_9_3_, setupuser4_.login_id as login_id4_, setupuser4_.last_name as last_name4_, setupuser4_.cti_user_code as cti_use14_4_, setupuser4_.full_name as full_name4_, setupuser4_.department_code as departm26_4_, setupuser4_.password_date as password4_4_, setupuser4_.display_name as display22_4_, setupuser4_.cti_password as cti_pas15_4_, setupuser4_.cti_extension as cti_ext17_4_, setupuser4_.cti_server as cti_server4_, setupuser4_.middle_name as middle_n8_4_, setupuser4_.clear_cache as clear_c20_4_, setupuser4_.title as title4_, setupuser4_.outlook_sync_confirm as outlook25_4_, setupuser4_.license_type as license23_4_, setupuser4_.description as descrip11_4_, setupuser4_.cti_station as cti_sta16_4_, setupuser4_.first_name as first_name4_, setupuser4_.action_set_code as action_18_4_, setupuser4_.startup_object_id as startup19_4_, setupuser4_.outlook_sync_frequency as outlook24_4_, setupuser4_.job_function_code as job_fun27_4_, setupuser4_.active as active4_, setupuser4_.password_expires as password5_4_, setupuser4_.password_value as password3_4_, setupuser4_.change_password as change_p6_4_, contacts5_.creation_date as creation5_5_, contacts5_.assignment_date as assignme2_5_, contacts5_.activity_id as activit10_5_, contacts5_.sort_order as sort_order5_, contacts5_.updated_date as updated_6_5_, contacts5_.primary_flag as primary_7_5_, contacts5_.contact_id as contact_id5_, contacts5_.attendance as attendance5_, contacts5_.created_by_user_code as created_8_5_, contacts5_.updated_by_user_code as updated_9_5_, contact6_.status_updated_by_user_code as status_21_6_, contact6_.contact_status_code as contact18_6_, contact6_.middle_name as middle_n4_6_, contact6_.status_updated_date as status_16_6_, contact6_.first_name_search as first_na7_6_, contact6_.do_not_solicit as do_not_12_6_, contact6_.updated_date as updated15_6_, contact6_.duplicate_check as duplica13_6_, contact6_.salutation_code as salutat19_6_, contact6_.external_id as externa11_6_, contact6_.last_name as last_name6_, contact6_.last_name_soundex as last_na10_6_, contact6_.last_name_search as last_nam8_6_, contact6_.creation_date as creatio14_6_, contact6_.suffix as suffix6_, contact6_.first_name as first_name6_, contact6_.greeting as greeting6_, contact6_.first_name_soundex as first_na9_6_, contact6_.updated_by_user_code as updated22_6_, contact6_.contact_method_code as contact17_6_, contact6_.created_by_user_code as created20_6_ from oncd_activity activity0_ inner join onca_action setupactio1_ on activity0_.action_code=setupactio1_.action_code inner join onca_result setupresul2_ on activity0_.result_code=setupresul2_.result_code left outer join oncd_activity_user users3_ on activity0_.activity_id=users3_.activity_id inner join onca_user setupuser4_ on users3_.user_code=setupuser4_.user_code left outer join oncd_activity_contact contacts5_ on activity0_.activity_id=contacts5_.activity_id inner join oncd_contact contact6_ on contacts5_.contact_id=contact6_.contact_id where ((users3_.primary_flag='Y')OR(users3_.primary_flag IS NULL))AND((contacts5_.primary_flag='Y')OR(contacts5_.primary_flag IS NULL))AND(setupactio1_.description LIKE @p0) order by  activity0_.due_date DESC
***
*** The following selects occur for each row returned by the original select -- I don't want them to occur ...
***
2005-12-28 11:55:08,434 [4868] DEBUG NHibernate.SQL - SELECT activityno0_.activity_id as activity1_0_, activityno0_.updated_date as updated_4_0_, activityno0_.note as note0_, activityno0_.updated_by_user_code as updated_5_0_, activityno0_.creation_date as creation3_0_, activityno0_.created_by_user_code as created_6_0_ FROM oncd_activity_note activityno0_ WHERE activityno0_.activity_id=@p0
2005-12-28 11:55:08,762 [4868] DEBUG NHibernate.SQL - SELECT contactnot0_.contact_id as contact_id0_, contactnot0_.updated_date as updated_4_0_, contactnot0_.note as note0_, contactnot0_.created_by_user_code as created_5_0_, contactnot0_.updated_by_user_code as updated_6_0_, contactnot0_.creation_date as creation3_0_ FROM oncd_contact_note contactnot0_ WHERE contactnot0_.contact_id=@p0
2005-12-28 11:55:08,950 [4868] DEBUG NHibernate.SQL - SELECT activityno0_.activity_id as activity1_0_, activityno0_.updated_date as updated_4_0_, activityno0_.note as note0_, activityno0_.updated_by_user_code as updated_5_0_, activityno0_.creation_date as creation3_0_, activityno0_.created_by_user_code as created_6_0_ FROM oncd_activity_note activityno0_ WHERE activityno0_.activity_id=@p0
2005-12-28 11:55:08,950 [4868] DEBUG NHibernate.SQL - SELECT contactnot0_.contact_id as contact_id0_, contactnot0_.updated_date as updated_4_0_, contactnot0_.note as note0_, contactnot0_.created_by_user_code as created_5_0_, contactnot0_.updated_by_user_code as updated_6_0_, contactnot0_.creation_date as creation3_0_ FROM oncd_contact_note contactnot0_ WHERE contactnot0_.contact_id=@p0
...


Is there a way to prevent NHibernate from selecting from this BLOB table, or to include the selection as a join in the main SQL query? My HQL query actually selects two different entities with related BLOB child entities, but I thought SQL Server 2000 could select multiple BLOBs in the same SQL query, so that shouldn't be a reason for NHibernate to use separate SQL selects ...


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 28, 2005 5:02 pm 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
If you can, change one-to-one association to many-to-one. Lazy loading only works for one-to-one's that are constrained, i.e. always present. The reason is explained in http://www.hibernate.org/162.html.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 28, 2005 5:32 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
From the above topic:

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.

So the resume: if your B->C mapping is mandatory (constrainted=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.


Huh? Why can't the original select do an outer join to the non-mandatory one-to-one table, selecting C's primary key, to see if C is present for each row? If the C's key column is null, there is no row due to the outer join, so it can tell the difference in the original SQL statement whether C exists or not for each row of the result set. Isn't this much more efficient than doing individual selects after the main select? The database server may do the same amount of work on the back end, and somewhat more if you start accessing some of the C entities, but if the initial query returns 1000 rows, it means the difference between NHibernate submitting 1 SQL statement (or 5 if you accessed 4 C's) vs. 1001.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 28, 2005 5:39 pm 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
Regarding the statement

Proxy does not help here because proxy itself in already non-null object.

Huh? Why can't proxies do the job? What's important is that B is proxied, not C. If B is a proxy, and the property B.C is mapped with property access, then B's proxy can remember if a C was found associated to the B from the original query. If it was found, it would fetch C on first access of B.C; if not, B.C would always return null.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Dec 28, 2005 7:59 pm 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
Quote:
Huh? Why can't proxies do the job? What's important is that B is proxied, not C. If B is a proxy, and the property B.C is mapped with property access, then B's proxy can remember if a C was found associated to the B from the original query. If it was found, it would fetch C on first access of B.C; if not, B.C would always return null.


That's not how proxies work in Hibernate 2.1 and NHibernate. Proxies are only used for lazy loading, after the object (B) is loaded, the calls are forwarded to it directly, so B.C will always call the real method of the loaded C. B's proxy doesn't try to remember anything about the B it is proxying, only whether it was loaded already or not.

Quote:
Huh? Why can't the original select do an outer join to the non-mandatory one-to-one table, selecting C's primary key, to see if C is present for each row?


Because in the general case (no blobs, just plain small columns) joining an additional table is much more expensive than getting a few extra columns from an already joined table. So if you join a table, you might as well get the entire object for a minimal additional cost. Blobs change this of course, but there isn't any way to tell NH about the change.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 29, 2005 12:02 am 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
Quote:
Blobs change this of course, but there isn't any way to tell NH about the change.


Huh? The blob columns are mapped in the .hbm.xml file specifically with NHibernate "blob" types. NHibernate appears to have sufficient information to distinguish the two cases.

Nothing personal, but I asked "why can't" proxies be smart enough to handle this, not "why aren't they". It appears that they could handle this if collectively we decided to make them work that way ...

BTW, does Hibernate 3.X support lazy loading of one-to-one (and many-to-one) properties?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 29, 2005 6:46 am 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
Yes, Hibernate 3 can load individual properties lazily, though I don't know now if it requires the use of bytecode instrumentation for that or not...


Top
 Profile  
 
 Post subject:
PostPosted: Fri Dec 30, 2005 12:46 am 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
Sounds good -- any rough plans at this point for when Hibernate 3.x features will be ported to NHibernate? :-)

Bytecode instrumentation ... hmmm ... can you elaborate a little on that, or give us a link that explains it?


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 8 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.