Sunday, August 2, 2009

Composite Key(s)

Using composite-key for a table with not 1 but more than 1 primary keys:

One of the major rule hibernate enforces is that there should be a primary key in any given table and thus its respective hbm file. This is important not only for the unique identification of each row from other but also for mapping the different tables by foreign keys.

However, we may come under situations where we might either not have any primary key at all or may have more than one. When we said more than one, it means either we created a composite primary key (primary key with more than 1 column) while creating the table itself or we may have unique constraints on one or more columns of the table which does not have any primary key. In both the situations, hibernate allow us to deal with the concept of something called as composite-key(s).

Let us consider a small example that will give a clear idea on use of composite-key in hibernate:
Create the following two tables in oracle database:

Create table addr(id integer primary key, street varchar2(20), city varchar2(20));
Create table compo_person(name varchar2(30), addr_id integer references addr(id), age integer, primary key(name,addr_id));


As you can see, we have created a composite primary key on compo_person table i.e. a table with not 1 but 2 columns acting as primary keys. We will consider these tables for looking at how hibernate composite keys work:

Create 3 different pojos namely Address, Person and CompositePerson as follows:

Address: int id, String street, String city
Person implements Serializable: String name, Address address;
CompositePerson: Person person, int age;

Now, create a file called CompositeExample.hbm.xml as follows:


<hibernate-mapping>
<class name="CompositePerson" table="compo_person">
<composite-id name="person" class="Person">
<key-property name="name" type="string" column="name"/>
<key-many-to-one name="address" class="Address" column="addr_id"/>
</composite-id>
<property name="age" column="age" type="java.lang.Integer"/>
</class>

<class name="Address" table="addr">
<id name="addressId" column="id" type="java.lang.Integer">
<generator class="assigned"/>
</id>
<property name="street" column="street" type="string"/>
<property name="city" column="city" type="string"/>
</class>
</hibernate-mapping>

From the above file, if you see, Address is a simple mapping mapped normally to the addr table. However, since we have compo_person table with a composite primary key, i.e. 2 primary keys, we need to tell hibernate about it and map it accordingly in this file. This is done through the tag <composite-id>. The difference between normal <id;> and <composite-id> is that while <id> allows us to just specify one property of the class as a primary key, <composite-id> allows us to add a set of columns.
It is always a good idea to create a seperate class which contains of just those columns that are added as composite primary keys in the table. For this purpose, we have created a seperate class called Person and added address and name fields in it which as we can see from the hbm file, are mapping to addr_id and name columns from the table. Thus, Person class will act as a class for holding our Composite keys.

Apart from this composite primary key, we also attached the other normal property “age” inside CompositePerson class.
Now, let us see, how do we use this composite key to save and load the values to/from the database:


Save composite key values:

Address address = new Address(3,"street2","city2");
session.save(address);
Person person = new Person("name3",address);
CompositePerson compositePerson = new CompositePerson(person,23);

System.out.println("Saving composite person now");
session.save(compositePerson);
session.flush();

As we see from the above example, save is no different than saving from our normal classes. We first save the address, then set that address to the person as it is one of the parameter for Person class.

Load composite key values:

CompositePerson compositePerson2 = (CompositePerson) session.load(CompositePerson.class, person);


The only difference here is, instead of passing a single id parameter which we pass in case of normal primary key, we pass a composite key reference i.e. we pass a person instance since person instance holds the reference to different name(&)address columns. As we know, load method will search the database with a given id that we specify but since we have a composite id in this case, we pass that to load a particular compositeperson. Remember that since Each person is different (separate name & address combination), we always get only one CompositePerson object just as we would have got if we had used a normal primary key and passed that to the load method.

The above statement will generate an sql query like:

"select p.name, a.addr_id, a.street, a.city, c.age from person p, addr a, compo_person c where c.name = p.name and c.addr_id = a.id"

No comments:

Post a Comment