Thursday, September 4, 2008

Mapping Associations in Hibernate

Consider a scenario when your application has an Item and associated Bids with it. Each bid has its own life cycle & its own primary key in the database. Bid class also holds the reference to the Item. So, in our data model, Item_table has items and Bid_table has bids which also has reference key to the Item_table. In our domain model, Item is a class which has a set of Bids associated with it. Also, Bid is a class which has corresponding item. Bottom line, Item can has many bids and each Bid can only be associated with one item.

This is a typical one to many mapping problem [Item to Bid.] If we talk about writing this logic in Java, we'll probably write something like below in the Item class.

public void addBid(Bid bid){
bids.add(bid);
bid.setItem(this);
}


We add the bid in bids and associate the item to the bid as well. Java does not manage associations neither does Hibernate. You have to explicitly make a link at both ends of the relation. Item to Bid, and Bid to Item as shown above.

How do you apprehend the above method. There are two updates. First, the bid got added to bids which is in Item. Second, we associated the Item to the Bid. Java as well as Hibernate see this as two updates. But if we think in terms of Database, we just added a Bid in a Bid_table table and that’s all. There is only one update !!!!

Hibernate supports transitive persistence model and we can tell hibernate to understand this relation using a property 'inverse="true"'. I guess example will make things more clear.

//Bid.class and its corresponding mapping file. Notice many-to-one element which is acting like, rather is, reference key.

public class Bid {
private Long id = new Long(-1);
private Item item;
private Double amount;
private Date bidDate;
public Bid(){}
public Bid(Double amount){
this.amount = amount;
this.bidDate = new Date();
}
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
...... Other getter and setters.
}
<class name="association.Bid" table="BID">
<id name="id" column="BID_ID" type="long">
<generator class="increment"/>
</id>
<property name="amount" column="amount" type="java.lang.Double" />
<property name="bidDate" column="BID_DATE" type="java.util.Date" />
<many-to-one
name="item"
column="ITEM_ID"
class="association.Item"
not-null="true"/>
</class>

//Item.class as its corresponing mapping file
public class Item {
private Long id;
private Set<Bid> bids = new HashSet<Bid>();
private String name;
private String description;
private Double initialAmount;
public Item(){}
public Item(String name,String description, Double initialAmount){
this.name = name;
this.description = description;
this.initialAmount = initialAmount;
}
public void addBid(Bid bid){
bids.add(bid);
bid.setItem(this);
}
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
....... other getters and setters
}
<class name="association.Item" table="ITEM">
<id name="id" column="ITEM_ID" type="long">
<generator class="increment"/>
</id>
<property name="name" column="name" type="java.lang.String" />
<property name="description" column="description" type="java.lang.String" />
<property name="initialAmount" column="initial_price" type="java.lang.Double" />
<set name="bids">
<key column="ITEM_ID"/>
<one-to-many class="association.Bid"/>
</set>
</class>

// TEST Class for this association.
public class ItemBidTest {
public static void main(String[] args){
Item item = new Item("SKU-1","SKU-1 Description", new Double(10.98));
item.addBid(new Bid(new Double(11.10)));
item.addBid(new Bid(new Double(11.15)));
item.addBid(new Bid(new Double(11.20)));
saveItem(item);
}
public static void saveItem(Item item){
Transaction tx = null;
Session session = InitSessionFactory.getInstance().getCurrentSession();
try {
tx = session.beginTransaction();
session.save(item);
tx.commit();
} catch (HibernateException e) {
e.printStackTrace();
if (tx != null && tx.isActive())
tx.rollback();
}
}
}

----------OUTPUT, Mapping of bids in Item.hbm.xml is below ----------------------------

<set name="bids" cascade="all-delete-orphan">
<key column="ITEM_ID"/>
<one-to-many class="association.Bid"/>
</set>

Hibernate: insert into ITEM (name, description, initial_price, ITEM_ID) values (?, ?, ?, ?)
Hibernate: insert into BID (amount, BID_DATE, ITEM_ID, BID_ID) values (?, ?, ?, ?)
Hibernate: insert into BID (amount, BID_DATE, ITEM_ID, BID_ID) values (?, ?, ?, ?)
Hibernate: insert into BID (amount, BID_DATE, ITEM_ID, BID_ID) values (?, ?, ?, ?)
Hibernate: update BID set ITEM_ID=? where BID_ID=?
Hibernate: update BID set ITEM_ID=? where BID_ID=?
Hibernate: update BID set ITEM_ID=? where BID_ID=?

----------OUTPUT, MAPPING in Item.hbm.xml CHANGED to below -----------------------------

<set name="bids" cascade="all-delete-orphan" inverse="true">
<key column="ITEM_ID"/>
<one-to-many class="association.Bid"/>
</set>

Hibernate: insert into ITEM (name, description, initial_price, ITEM_ID) values (?, ?, ?, ?)
Hibernate: insert into BID (amount, BID_DATE, ITEM_ID, BID_ID) values (?, ?, ?, ?)
Hibernate: insert into BID (amount, BID_DATE, ITEM_ID, BID_ID) values (?, ?, ?, ?)
Hibernate: insert into BID (amount, BID_DATE, ITEM_ID, BID_ID) values (?, ?, ?, ?)




>>> We therefore got rid of extra update queries to database.