tags:

views:

39

answers:

1

Hi All

Short Summary of Problem

Hibernate is assuming that whenever someone is using subclass all the columns should be created in the top root of the class hierarchy irrespective of what table name has been provided. Can someone please explain the reasoning behind it and if there is way to bypass this code and somehow avoid invoice_id getting created in transaction table???

Details

I have a very deep class hierarchy in my domain model and want to use discriminator tag to support inheritance.

Initially i mapped my classes using join-subclass, however the tables involved have hundreds of thousands of records and it is turning out to be a performance bottleneck. So in essence i want to flatten my table structure without touching the domain and that's why we moved on to discriminator tag.

My hibernate mappings looks like(have deleted the extra column for sake of simplicity)

Transaction mapping

<class name="Transaction" table="transaction" abstract="true">
   ...
    <discriminator column="transaction_type" type="string"/>
</class>

CashBasedTrsansaction mapping

<subclass name="CashBasedTransaction" extends="Transaction" discriminator-value="CASH">
    <join table="cash_based_transaction">
        <key column="id" />
    </join>
    <subclass discriminator-value="BILLING" name="BillingTransaction">
        <join table="billing_transaction">
            <key column="id" />
            <many-to-one name="invoice" column="invoice_id" cascade="all" access="field" lazy="false">
            </many-to-one>
        </join>
        <subclass name="ChildBillingTransaction" discriminator-value="UPT">
            <join table="billing_transaction">
                <key column="id" />
                ...
            </join>
        </subclass>
        <subclass abstract="true" name="AnotherChildOfBillingTransaction" discriminator-value="LPT">
             <subclass name="SuperChildOfBillingTransaction" discriminator-value="OCLPT">
                 <join table="billing_transaction">
                     <key column="id" />
                     ...
                 </join>
             </subclass>
             <subclass name="AnoherSuperChildOfBillingTransaction" discriminator-value="SLPT">
                 <join table="billing_transaction">
                     <key column="id" />
                     ...
                 </join>
             </subclass>
         </subclass><!--End Of AnotherChildOfBillingTransaction-->
     </subclass><!--End Of BillingTransaction-->
</subclass><!--End Of CashBasedTransaction-->

Invoice mapping

<class name="Invoice" table="invoice">
    ...
    <bag name="billingTransactions" access="field" cascade="all" inverse="true" table="billing_transaction">
        <key column="invoice_id" />
        <one-to-many class="BillingTransaction" />
    </bag>
</class>

What i want to achieve : I want to flatten out table structure after billing_transaction. In other words i want to have only three tables in the database

  • transaction
  • cash_based_transaction
  • billing_transaction(this table should hold all the columns after flattening out all the subclasses)

P.S : Please note that i want to flatten out table structure not from the aggregate root(read transaction) but somewhere down the line in my class hierarchy, billing_transaction in this case.

Problem : Hibernate is creating a column "invoice_id" in transaction table(this is wrong) as well as in billing_transaction(this is correct). On further debugging i found some interesting results and need some feedback/advice.

  • Hibernate creates a column invoice_id in billing_transaction which is what i want.

  • Hibernate also creates another column with same name i.e. invoice_id in Transaction table which is not what i want.

Now this is frustrating. Even though i have mentioned the table name(Billing_Transaction) in invoice.hbm and have set inverse="true" yet hibernate goes ahead and create a column invoice_id in transaction table even though there is already one in Billing_Transaction. I was expecting that since i am giving it the table name hibernate should take that name and check if billing_transaction has invoice_id or not... Instead hibernate is doing exactly opposite. It is completely ignoring the table name that i have provided and reaching out to the most super class i.e. Transaction. There it creates invoice_id column when it finds out that no such column exists. As a result i have two invoice_id column sitting in my tables. One in billing_transaction where i want it to be and another in transaction table where i don't want it to be.

I have found out the code which is causing it. In org.hibernate.mapping.Subclass table is identified using the below given code

public Property getIdentifierProperty() {
  return getSuperclass().getIdentifierProperty();

}

In other words hibernate is assuming that whenever someone is using subclass all the columns should be create in the top root of the class hierarchy irrespective of what table name has been provided. Can someone please explain the reasoning behind it and if there is way to bypass this code and somehow avoid invoice_id getting created in transaction table???

+1  A: 

If you have

/**
  * MAPPED TO TRANSACTION
  */
public class Transaction {...}

/**
  * MAPPED TO CASH_BASED_TRANSACTION
  */
public class CashBasedTransaction extends Transaction {...}


/**
  * MAPPED TO BILLING_TRANSACTION
  */    
public class BillingTransaction extends CashBasedTransaction {...}

And Invoice needs a bag of BillingTransaction (and its superclass CashBasedTransaction) (and its grandfathered superclass Transaction, right).

This is accomplished by using a foreign INVOICE_ID. Yes, it makes sense just retrieve BillingTransaction. Its superclass can be retrieved by using its related primary key. So maybe CashBasedTransaction and Transaction does not need INVOICE_ID. Can it be a bug ??? maybe yes, maybe not. Hibernate Team may have some reason to generate this SQL.

keep in mind: keep your code simple and just map what customers needs. If customers needs other requirements, refactoring (and Unit Testing) is your best friend.

Advices

  • Inheritance can be mapped as a has-a instead of is-a

So if you have

/**
  * CashBasedTransaction IS A Transaction
  */
public class CashBasedTransaction extends Transaction {...}

You can refactor as

public class CashBasedTransaction {

   /**
     * CashBasedTransaction HAS A Transaction
     */
    Transaction transaction;

}

UPDATE

Workaround to avoid inheritance mapping: remove all of subclass and use join to map its inherited properties. Just a simple mapping (Adapt to your mapping)

Parent

public class Parent {

    private String parentProperty;
    public String getParentProperty() { return parentProperty; }
    public void setParentProperty(String parentProperty) { this.parentProperty = parentProperty; }

}

Child

public class Child extends Parent {

    private Integer id;
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }

}

Invoice

public class Invoice {

    private Integer id;
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }

    private Collection<Child> childList = new ArrayList<Child>();
    public  Collection<Child> getChildList() { return childList; }
    public void setChildList( Collection<Child> childList) { this.childList = childList; }

}

The mapping is shown as follows

<?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"&gt;
<hibernate-mapping package="mapping._3863924.model.domain">
    <class name="Child" table="CHILD">
        <id name="id">
            <generator class="native"/>
        </id>
        <join table="PARENT">
            <key column="CHILD_ID"/>
            <property name="parentProperty"/>
        </join>
    </class>
    <class name="Invoice" table="INVOICE">
        <id name="id">
            <generator class="native"/>
        </id>
        <bag name="childList" table="CHILD">
            <key column="INVOICE_ID"/>
            <one-to-many class="Child"/>
        </bag>
    </class>
</hibernate-mapping>

Standard output shows

create table CHILD (
    id integer generated by default as identity (start with 1),
    INVOICE_ID integer,
    primary key (id)
)

create table INVOICE (
    id integer generated by default as identity (start with 1),
    primary key (id)
)

create table PARENT (
    CHILD_ID integer not null,
    parentProperty varchar(255),
    primary key (CHILD_ID)
)

Notice PARENT table does not contain INVOICE_ID. (Is it what you want, do not ?)

Arthur Ronald F D Garcia
First of all thanks a lot for replying....Yes you are right that we could have used composition vs inheritance, however there are some other objects which has transactions. Now if we go by the suggested approach we will end up having a reference of BillingTransactions, SomeOtherTransactions etc in the same object. Now is a right approach, may be not. But i want to ensure there is nothing out there before i go ahead and change my class hierarchy.
@user461252 I will try some scenario which can help you. Usually, beginners StackOverflow users ask for help **and go away**. It explains why your question has not been answered so soon. If my answer fullfil your needs click on the Up arrow or mark as accepted ("v" icon)
Arthur Ronald F D Garcia
@user461252 See **UPDATE** and *mark as accepted if it fullfil your needs*, ok
Arthur Ronald F D Garcia
Here is my upvote for taking time to read the question... and to answer.
Pascal Thivent
@Pascal Thivent I appreciate your support, Pascal. On weekend, i will see your answers (and collect useful advices) about Agile / Scrum
Arthur Ronald F D Garcia