Hibernate Books

All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 5 posts ] 
Author Message
 Post subject: Lazy Loading subclasses (table per subclass discriminator)
PostPosted: Wed Sep 17, 2008 3:26 am 
Newbie

Joined: Wed Sep 17, 2008 2:59 am
Posts: 10
I'm trying to find out how to lazily load a subclass when using the table per subclass inheritance mapping using a discriminator column. The reason being, in about half of the time, we don't want or need to access the properties of the subclasses, we simply need the properties from the parent class.

But it seems that, even specifying "lazy=true" on the subclass mapping / element, Hibernate still ( assuming "fetch=select" on the join element of the subclass mapping ) eagerly fetches the data from the table mapped to the subclass as soon as I access just the property from the parent class.

I tried specifying either the subclass or the parent class in the call to load(), hoping that somehow specifying the parent class will give me a lazily loaded subclass:

1) When specifying the subclass in load(), Hibernate uses an inner join against the table mapped to the subclass.

2) When specifying the parent class in load(), Hibernate uses 2 separate SELECTs ( one against the table mapped to the parent class, another against the table mapped to the subclass ).

I was hoping / expecting that Hibernate will only do a separate SELECT from the table mapped to the subclass only when I access / set the properties of the subclass.

What am I missing ?

Below are sample a sample mapping file, SQL scripts, and sample code, and output:

Hibernate version: 3.3.1 GA

Mapping documents:

Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
   <class name="Payment" table="PAYMENT" lazy="true">
       <id name="id" type="integer" column="PAYMENT_ID">
           <generator class="native"/>
       </id>
       <discriminator column="PAYMENT_TYPE" type="string"/>
       <property name="amount" column="AMOUNT"/>
       <subclass name="CreditPayment" discriminator-value="CREDIT" lazy="true">
           <join table="CREDIT_PAYMENT" fetch="select">
               <key column="PAYMENT_ID"/>
               <property name="cardType" column="CARD_TYPE" type="string"/>
           </join>
       </subclass>
       <subclass name="ChequePayment" discriminator-value="CHEQUE" lazy="true">
           <join table="CHEQUE_PAYMENT" fetch="select">
               <key column="PAYMENT_ID"/>
               <property name="chequeNumber" column="CHEQUE_NUMBER" type="string"/>
           </join>
       </subclass>
    </class>
</hibernate-mapping>



SQL scripts:

Code:
create table payment(
payment_id integer,
payment_type varchar(10),
amount numeric);

alter table payment add constraint payment_pk primary key ( payment_id ) ;


create table credit_payment(
payment_id integer,
card_type varchar(10)
);

alter table credit_payment add constraint credit_payment_pk primary key ( payment_id );
alter table credit_payment add constraint credit_payment_fk1 foreign key ( payment_id ) references payment ( payment_id );

create table cheque_payment(
payment_id integer,
cheque_number varchar(20)
);

alter table cheque_payment add constraint cheque_payment_pk primary key ( payment_id );
alter table cheque_payment add constraint cheque_payment_fk1 foreign key ( payment_id ) references payment ( payment_id );

insert into payment( payment_id, payment_type, amount ) values ( 1, 'CREDIT', 500.00 );
insert into credit_payment( payment_id, card_type ) values ( 1, 'VISA' );

insert into payment( payment_id, payment_type, amount ) values ( 2, 'CHEQUE', 200.00 );
insert into cheque_payment( payment_id, cheque_number ) values ( 2, '12345' );

commit;


Code between sessionFactory.openSession() and session.close():
Code:
      Payment creditPayment = (Payment) session.load(CreditPayment.class, 1);
      creditPayment.getPaymentType();
      
      Payment chequePayment = (Payment) session.load(Payment.class, 2);
      chequePayment.getPaymentType();


Full stack trace of any exception that occurs: n/a

Name and version of the database you are using: Oracle 10G Release 2

The generated SQL (show_sql=true):

When specifying the subclass in load(), an inner join is used when a property of the superclass is accessed via the getPaymentType() method:

Code:
select creditpaym0_.PAYMENT_ID as PAYMENT1_0_0_, creditpaym0_.AMOUNT as AMOUNT0_0_, creditpaym0_1_.CARD_TYPE as CARD2_1_0_ from PAYMENT creditpaym0_ inner join CREDIT_PAYMENT creditpaym0_1_ on creditpaym0_.PAYMENT_ID=creditpaym0_1_.PAYMENT_ID where creditpaym0_.PAYMENT_ID=1 and creditpaym0_.PAYMENT_TYPE='CREDIT'


When specifying the superclass in load(), two SELECTs are made when a property of the superclass is accessed via the getPaymentType() method:

Code:
select payment0_.PAYMENT_ID as PAYMENT1_0_0_, payment0_.AMOUNT as AMOUNT0_0_, payment0_.PAYMENT_TYPE as PAYMENT2_0_0_ from PAYMENT payment0_ where payment0_.PAYMENT_ID=2

select payment_2_.CHEQUE_NUMBER as CHEQUE2_2_ from CHEQUE_PAYMENT payment_2_ where payment_2_.PAYMENT_ID=2


Debug level Hibernate log excerpt:

Debug level output for the code:
Code:
      Payment creditPayment = (Payment) session.load(CreditPayment.class, 1);
      creditPayment.getPaymentType();


e.g.: When specifying the subclass in load():

Code:
initializing proxy: [CreditPayment#1]
loading entity: [CreditPayment#1]
about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
select creditpaym0_.PAYMENT_ID as PAYMENT1_0_0_, creditpaym0_.AMOUNT as AMOUNT0_0_, creditpaym0_1_.CARD_TYPE as CARD2_1_0_ from PAYMENT creditpaym0_ inner join CREDIT_PAYMENT creditpaym0_1_ on creditpaym0_.PAYMENT_ID=creditpaym0_1_.PAYMENT_ID where creditpaym0_.PAYMENT_ID=? and creditpaym0_.PAYMENT_TYPE='CREDIT'
select creditpaym0_.PAYMENT_ID as PAYMENT1_0_0_, creditpaym0_.AMOUNT as AMOUNT0_0_, creditpaym0_1_.CARD_TYPE as CARD2_1_0_ from PAYMENT creditpaym0_ inner join CREDIT_PAYMENT creditpaym0_1_ on creditpaym0_.PAYMENT_ID=creditpaym0_1_.PAYMENT_ID where creditpaym0_.PAYMENT_ID=1 and creditpaym0_.PAYMENT_TYPE='CREDIT'
about to open ResultSet (open ResultSets: 0, globally: 0)
result row: EntityKey[CreditPayment#1]
about to close ResultSet (open ResultSets: 1, globally: 1)
about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
resolving associations for [CreditPayment#1]
done materializing entity [CreditPayment#1]
initializing non-lazy collections
done entity load



Debug level output for:

Code:
      Payment chequePayment = (Payment) session.load(Payment.class, 2);
      chequePayment.getPaymentType();


e.g. When specifying the parent class in load():

Code:
initializing proxy: [Payment#2]
loading entity: [Payment#2]
about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
select payment0_.PAYMENT_ID as PAYMENT1_0_0_, payment0_.AMOUNT as AMOUNT0_0_, payment0_.PAYMENT_TYPE as PAYMENT2_0_0_ from PAYMENT payment0_ where payment0_.PAYMENT_ID=?
select payment0_.PAYMENT_ID as PAYMENT1_0_0_, payment0_.AMOUNT as AMOUNT0_0_, payment0_.PAYMENT_TYPE as PAYMENT2_0_0_ from PAYMENT payment0_ where payment0_.PAYMENT_ID=2
about to open ResultSet (open ResultSets: 0, globally: 0)
result row: EntityKey[Payment#2]
about to open PreparedStatement (open PreparedStatements: 1, globally: 1)
select payment_2_.CHEQUE_NUMBER as CHEQUE2_2_ from CHEQUE_PAYMENT payment_2_ where payment_2_.PAYMENT_ID=?
select payment_2_.CHEQUE_NUMBER as CHEQUE2_2_ from CHEQUE_PAYMENT payment_2_ where payment_2_.PAYMENT_ID=2
about to close PreparedStatement (open PreparedStatements: 2, globally: 2)
about to close ResultSet (open ResultSets: 1, globally: 1)
about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
resolving associations for [ChequePayment#2]
done materializing entity [ChequePayment#2]
initializing non-lazy collections
done entity load


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 17, 2008 4:44 pm 
Newbie

Joined: Wed Sep 17, 2008 2:59 am
Posts: 10
So it seems that others who have asked this question:

http://forum.hibernate.org/viewtopic.php?t=930208
http://forum.hibernate.org/viewtopic.php?t=934053
http://forum.hibernate.org/viewtopic.php?t=935155

... appears not to have a solution. What's the point of having / allowing the lazy attribute in the subclass element then ?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Sep 18, 2008 12:28 am 
Newbie

Joined: Wed Sep 17, 2008 2:59 am
Posts: 10
Let me rephrase my question:

I actually WANT hibernate to return me the correct subclass. However, I only want hibernate to do a SELECT from the table mapped to the subclass only when I actually access the properties of the subclass. If I only access the properties of the parent class, then there should be no need for Hibernate to SELECT from the table mapped to the subclass, even though Hibernate returned me the correct subclass.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Sep 18, 2008 6:46 am 
Newbie

Joined: Wed Sep 17, 2008 2:59 am
Posts: 10
After further thinking about it .... the reason probably why Hibernate does not do what I was expecting it to do is because of the other inheritance strategy, which still uses a table per concrete class but no discriminator. In that case, the only real way for Hibernate to find out what subclass to use is to actually select or do an outer join against the tables mapped to the subclasses.

However, I think the table per subclass using a discriminator can be exception, since Hibernate already knows what subclass to use based on the discriminator value, so that it does not really need to fetch or select from the table mapped to the subclass unless the properties of the subclass are accessed.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Sep 25, 2008 12:30 am 
Newbie

Joined: Wed Sep 17, 2008 2:59 am
Posts: 10
I have found an alternative solution, though not the best.

What I have done, using the previous mapping file as an example, is to map the same class twice ( as per http://www.hibernate.org/hib_docs/v3/reference/en-US/html/mapping-entityname.html ), but the second mapping has no subclasses and uses a different entity name.

Here's the updated mapping file using the example from my original post:

Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
   <class name="Payment" table="PAYMENT" lazy="true">
       <id name="id" type="integer" column="PAYMENT_ID">
           <generator class="native"/>
       </id>
       <discriminator column="PAYMENT_TYPE" type="string"/>
       <property name="amount" column="AMOUNT" type="double"/>
       <subclass name="CreditPayment" discriminator-value="CREDIT" lazy="true">
           <join table="CREDIT_PAYMENT" fetch="select">
               <key column="PAYMENT_ID"/>
               <property name="cardType" column="CARD_TYPE" type="string"/>
           </join>
       </subclass>
       <subclass name="ChequePayment" discriminator-value="CHEQUE" lazy="true">
           <join table="CHEQUE_PAYMENT" fetch="select">
               <key column="PAYMENT_ID"/>
               <property name="chequeNumber" column="CHEQUE_NUMBER" type="string"/>
           </join>
       </subclass>
    </class>
    <class name="Payment" entity-name="PaymentNoInheritance" table="PAYMENT" lazy="true">
       <id name="id" type="integer" column="PAYMENT_ID">
           <generator class="native"/>
       </id>
       <property name="paymentType" column="PAYMENT_TYPE" type="string"/>
       <property name="amount" column="AMOUNT" type="double"/>
    </class>
</hibernate-mapping>

Thus, if I want to get a list of Payment without having to hit the tables mapped to the subclasses, I do:

Code:
session.list( "select entity from PaymentNoInheritance entity" );


The draw back of this is that instances of objects returned from the list using the PaymentNoInheritance entity name versus instances of objects from Payment are different as far as Hibernate's persistent context is concerned, even though they have same ID ( same row in the database ).


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 5 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.