Showing posts with label Hibernate. Show all posts
Showing posts with label Hibernate. Show all posts

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"

Saturday, August 1, 2009

Hibernate get() Vs load()

Hibernate get() Vs load()
1. Load method will not hit the database until any field/property of the instance that we load is accessed i.e
a) User u = (User) session.load(User.class, new Long(1));
b) System.out.println("User is: "+u.getName());

In the above case, the database hit is performed at step (b) where we are actually accessing a property of the user by calling its getName() method

Get method will perform the database hit immediately when it is called. i.e.
a) User u = (User) session.get(User.class, new Long(1));
b) System.out.println("User is: "+u.getName());

In above case, the database hit is performed at step (a) itself.

2. With load, We cannot access the properties of a JavaBean instance after the session is closed. i.e. the scope of the instance obtained from the load is limited to a session context in which it is called. i.e.
a) User u = (User) session.load(User.class, new Long(1));
b) System.out.println("User is: "+u.getName());
c) session.close();
d) System.out.println("User is: "+u.getName());

In above case, the step 'b' would work fine since we are accessing the user instance in the scope of the session. However, the step 'd' would raise 'LazyInitializationException' because the session is closed
Get method Instances will be active even when the session is closed. i.e.
a) User u = (User) session.get(User.class, new Long(1));
b) System.out.println("User is: "+u.getName());
c) session.close();
d) System.out.println("User is: "+u.getName());

In above case, both steps 'b' and 'd' would work fine

3. Load method will throw an ObjectNotFoundException if the instance that we are trying to load does not exist in the database with the primary key value that we supply.i.e.
a) User u = (User) session.load(User.class, new Long(1));
b) System.out.println("User is: "+u.getName());

In above case, when the load is called and when the database hit is performed at step 'b', if the user with id 1 is not existing in the database, we get 'ObjectNotFoundException' at step 'b' since it hits the database at step 'b'
Get method will return null if the instance with the id we specify is not found, so we can check for null values

Small gottcha with session.load():
One Very important we must note is that when we give the following kind of statement:
User u = (User) session.load(User.class, new Long(15));
Then if we just pass that instance variable 'u' as it is to some other Class (like address.setUser(u)) without accessing any of u's property (like u.getName(), but NOT u.getId()), or if we just call u.getId() there itself, we wont get any exception and u.getId() will print whatever id we have passed for loading the user (15 in this case) even if that row doesnot exist in the database.
The reason is, in the load method, the second parameter passed will be considered and set as the id parameter by default for whatever is being loaded and after some other property is accessed like getName() etc, it will be overriden (which again would be same value if the user with that id exist in the database otherwise an exception will be thrown at that point).
To clarify the above point, consider the following example:
class User {
private int id;
private String name;
public User(int id,String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String Name() {
return name;
}

public String toString() {
return "User id = "+this.getId();
}
}


Now, Consider another class like Address as:

class Address {
private int id;
private User user;

public Address(int id, User user) {
this.id = id;
this.user = user;
}
}


Now, let us say that currently there is no user in the database.
Then,
User user = (User) session.load(User.class, new Integer(1));
System.out.println("User id = "+user.getId());
Address address = new Address(1,user);
session.save(address);

The first 3 lines from above code works just fine and we wont get any exception (even though no users are there in database). Here,
second line will give the output as: User id = 1
third line will consider the dummy user itself.
However, when we try to save the address which has a user associated with it through a foreign key relation and since we did not find any user, the batch update that is being performed for saving address along with its user fails and we get Could not execute JDBC Batch update exception.

However, if change the above example as follows:

User user = (User) session.load(User.class, new Integer(1));
System.out.println(user); (or) System.out.println(user.getName());

Then, the actual database hit is performed and we get the exception we wanted. This is because, we are performing the toString method on user in first case (Which is overriden and now returning the actual id of the user through its this.getId). Even if we dont override the toString() method, and try to log the user on console as System.out.println(user), ObjectNotFoundException would be thrown . And in second case, we are accessing a property other than id i.e. getName() is called on u, so the database is hit and it will NOW try to get the user details with that id which could not be found.

This can be a considered as a bit of a gottcha in load method of session since it is mentioned that load() will perform the hit as soon as any of the property of that particular instance which is being loaded is accessed and if it is not found, we get ObjectNotFoundException immediately. However, the 2 cases in which we consider we do not get the ObjectNotFoundException is:
1. We access the id property
2. We pass the loaded instance to another instance without accessing any of loaded instance's properties.

Friday, July 31, 2009

Object states in hibernate

Object states in hibernate:
An object in a hibernate can be in one of the following states:

1. Transient
2. Persistent
3. Detached

1. Transient: An object which is just instantiated using the "new" operator but not associated with any session in hibernate is said to be in Transient state. In other words, a transient object doesnot have any existing representation in the database .
Ex: User user = new User(); //Here, we just instantiated but did not associated it with the session. So, it user is said to be in transient state.


2. Persistent: An object is said to be in persistent state under the following scenarios:
a) It is associated with a hibernate session in some way like get, load, save, saveOrUpdate etc and is in the scope of an active transaction within that session.
ib) It has not been manually detached or evicted from the session either by calling evict on session (or) by closing the session (or) by ending the transaction through commit() or rollback()
Ex:
i) User user = new User();
ii) Session session = getSessionFactory().openSession();
iii) Transaction tx = session.beginTransaction();
iv) user = (User) session.get(User.class, new Long(1));
v) user.setName("Varun");
vi) session.saveOrUpdate(user);
vii) user.setEmail("varun@gmail.com");
viii) user.setJob("Software Engineer");
ix) tx.commit();


In the above example, the user object will be in transient state in step (i) and will continue to be in that state till step (iii). However, at step (iv), the user object is associated with a hibernate session through session.get(...) method. It is at this point of time, that the object of user instance is said move from 'transient' to 'persistent' state and it will continue to remain in that state till the last step i.e. step (ix) where the transaction is committed. One important thing to remember for hibernate's persistent state is that once an object is in persistent state (i.e. associated with the session), all the changes made to that instance are commited in the database when we commit the transaction. There is no need to call save or saveOrUpdate whenever we change few properties of that instance if it is still associated within a transaction. So, even though we called saveOrUpdate at step (v), but when the tx is committed at step (ix), we would find the user's email and job also saved with the values we supplied at steps (vii) and (viii)

3. Detached: An object or instance is said to be in 'detached' when it is no longer associated with a hibernate session or no mechanism exist to tie the instance to the database. This happens in either of the following conditions:
a) transaction is commited
b) session is closed
c) we programatically detach the instance by calling session.evict and passing the instance to it.
Furthermore, if you have any instances in scope while the transaction failed, those instances will become detached instances, because the transaction and the Hibernate Session with which they were associated are junked, and no longer capable of persisting the state of your data.
Points a and b from above are self explainatory. For example on programatically detaching the instance from the session, consider the following scenario:
User u = new User();
Session session = getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
user = (User) session.load(User.class, new Long(1));
user.setEmail("varun@gmail.com");
session.save(user);
user.setEmail("abc@gmail.com");
session.save(user);
session.evict(user);
user.setEmail("def@gmail.com");
tx.commit();

In the above example, the value of the user email would be set in database as "abc@gmail.com". Since after this, we have detached the user from the session, the session will no longer have a user associated with it. It has moved to detached instance. And once a user is detached, then, even if we change some of its values and commit the transaction under which it is runnning, those values wont be persisted to the database since the transaction just checks for the instances which are associated with the session under which it began.

If the above example is changed to:

user.setEmail("varun@gmail.com");
session.save(user);
user.setEmail("abc@gmail.com");
session.save(user);
user.setEmail("def@gmail.com");
tx.commit();
user.setEmail("ghi@gmail.com");
session.save(user);


Then, the value of the user's email property would be "def@gmail.com". This is because the instance gets detached automatically when the transaction which is opened under a session is committed.
One important rule Hibernate strictly enforces: only one instance of a class with a given unique primary key can be associated with the Session at a time. In the below shown example, if we try to re-associate the evicted user1 with the Hibernate Session, Hibernate will kick out to us a NonUniqueObjectException, indicating that it is already managing an instance that represents that particular database record.
User user1 = new User();
user1.setPassword("aaaaaa");
Long id = (Long)session.save(user1);
session.evict(user1);
User user2 = (User)session.get(User.class, id);
user1.setVerified(true);
session.saveOrUpdate(user1);
HibernateUtil.commitTransaction();