Loading Entities with Hibernate
In an earlier tutorial, we looked at the hypothetical example where you had the primary key of an entity, and wanted to query the database and have Hibernate return the unique User instance associated with that primary key. The Haitian Voodoo required to perform that retrieval task looked something like this:
public static User retrieveFromId(int idValue) { AnnotationConfiguration config = new AnnotationConfiguration(); config.addAnnotatedClass(User.class); SessionFactory factory; factory = config.configure().buildSessionFactory(); Session session = factory.getCurrentSession(); session.beginTransaction(); String queryString = "from User where id = :id"; Query query = session.createQuery(queryString); query.setInteger("id", idValue); Object queryResult = query.uniqueResult(); User user = (User)queryResult; session.getTransaction().commit(); System.out.print(user.getPassword()); return user; }
Taking a primary key, and using it to demonstrate variable injection and the retrieval of a unique entity was very androgologically sound, but the fact of the matter is, if you actually have the primary key of an entity, there is a much easier, or should I say "a couple of much easier ways" to retrieve the corresponding entity from the database. We'll demonstrate those ways by coding a few methods into a new class called the LoadRunner .
The LoadRunner Class
The LoadRunner class is going to have a runnable main method, and two static methods, named callGet and callLoad, which will be used to demonstrate the two different ways you can get at an entity given its associated primary key. For now, I've coded in all the redundant code we need to have each method connect to the database, create a session, and start a transaction. I know that all of this Hibernate plumbing code is getting repetitive; we'll factor it out into a HibernateUtil class soon enough.
package com.examscam; import org.hibernate.*;
import com.examscam.model.User; import org.hibernate.cfg.AnnotationConfiguration; public class LoadRunner { public static void callLoad(){ AnnotationConfiguration config = new AnnotationConfiguration(); config.addAnnotatedClass(User.class); SessionFactory factory= config.configure().buildSessionFactory(); Session session = factory.getCurrentSession(); session.beginTransaction(); /***** load code will go here *****/ session.getTransaction().commit(); } public static void callGet() { AnnotationConfiguration config = new AnnotationConfiguration(); config.addAnnotatedClass(User.class); SessionFactory factory= config.configure().buildSessionFactory(); Session session = factory.getCurrentSession(); session.beginTransaction(); /***** get code will go here *****/ session.getTransaction().commit(); } public static void main(String[] args) { LoadRunner.callLoad(); LoadRunner.callGet(); } }
The callGet Method
Within the session.beginTransaction() and the session.getTransaction().commit methods, we want to use the Hibernate Session to take a given primary key, and provide our program with the entity associated with that primary key in the database. It's all a pretty simple process. To get an entity from the underlying persistence store, all you have to do is call the Hibernate Session's get method, and provide two arguments: the Java class associated with the entity that your are retrieving, which in this case would be the User.class , and the actual primary key associated with the record. I know that I have a user in the database with a primary key of 1, so the number one will be used to test my methods.
Here's all the code you need to resurrect a User instance from the database using the Hibernate Session's load method:
session.beginTransaction(); User user = (User)session.get(User.class, new Long(1)); System.out.println(user.getPassword()); session.getTransaction().commit();
And that's about it! You simply call the load method of the Hibernate Session, provide the class type and the primary key as parameters, and then cast the object that is returned from Hibernate back into the appropriate Java type. From there, you can do just about anything with your JavaBean that you want. In this case, I simply print out the password of the User instance. It's all just so easy.
Now, as I mentioned, there are two ways to pull an entity from the database, one of which is to use the get method of the Hibernate Session, and the other way is to use the load method. Compare and contrast the following code snippet that invokes the Session's load method to the code snippet that invoked the Session's get method. The difference is subtle, to say the least.
session.beginTransaction(); User user = (User)session.load(User.class, new Long(1)); System.out.println(user.getPassword()); session.getTransaction().commit();
Hibernate load vs. Hibernate get Methods
Well, if you were to compare the load and get methods of the Hibernate Session, you'd think that they looked pretty darned similar; and you'd be correct, but there are subtle and very important differences.
First of all, the get method hits the database as soon as it is called. So, using the Hibernate Session's get method will always trigger a database hit. On the other hand, the load method only hits the database when a particular field of the entity is accessed. So, if we use the load method to retrieve an entity, but we never actually access any of the fields of that entity, we never actually hit the database. Pretty kewl, eh?
Well, actually, as kewl as the load method might sound, it actually triggers more problems than it solves, and here's why. If you initialize a JavaBean instance with a load method call, you can only access the properties of that JavaBean, for the first time, within the transactional context in which it was initialized. If you try to access the various properties of the JavaBean after the transaction that loaded it has been committed, you'll get an exception, a LazyInitializationException, as Hibernate no longer has a valid transactional context to use to hit the database.
So, while this code will work just fine?..
session.beginTransaction(); User user=(User)session.load(User.class, new Long(1)); System.out.println(user.getPassword()); session.getTransaction().commit();
.. this code will fail ?..
session.beginTransaction(); User user=(User)session.load(User.class, new Long(1)); session.getTransaction().commit(); System.out.println(user.getPassword());
.. and generate the following er ror , telling you that since the transaction was committed, there was no valid Session in which a read transaction against the database could be issued:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
So, the big thing to take away from this is that with the load method, you can't really use your loaded JavaBeans after the transaction has been committed, whereas, with the get method you can, because all of the various properties of a JavaBean retrieved through the get method are initialized right away.
Loading Non-Existent Records
An important scenario under which you need to contrast the load and get methods of the Hibernate Session has to do with what happens when you provide a primary key that doesn't actually exist in the database. Well, with the get method, you are simply returned a null object, which is no big deal.
With the load method, there's also no initial problem when you provide an invalid primary key to the method. From what you can tell, Hibernate appears to hand you back a valid, non-null instance of the class in which you are interested. However, the problems start when you actually try to access a property of that instance - that's where you run into trouble.
Remember how I said the load method doesn't hit the database until a property of the bean is requested? Well, if you've provided a primary key that doesn't exist in your database to the load method, when it does go to the database for the first time, it won't be able to find the non-existent, associated record, and your code will cough up big time. In fact, looking up a field based upon a non-existent primary key with the Hibernate Session's load method triggers the following error:
org.hibernate.ObjectNotFoundException:
No row with the given identifier exists: [User#123]
public Object get (Class clazz, Serializable id) throws HibernateException
--Return the persistent instance of the given entity class with the given identifier, or null if there is no such persistent instance. (If the instance is already associated with the session, return that instance or proxy.)
public Object load (Class theClass,Serializable id) throws HibernateException
--Return the persistent instance of the given entity class with the given identifier, assuming that the instance exists. You should not use this method to determine if an instance exists (use get() instead). Use this only to retrieve an instance that you assume exists, where non-existence would be an actual error.
-Regurgitated Hibernate API JavaDoc
package com.examscam; import org.hibernate.*; import com.examscam.model.User; import org.hibernate.cfg.AnnotationConfiguration; public class LoadRunner { public static void main(String[] args) { LoadRunner.callLoad(); LoadRunner.callGet(); } public static void callLoad(){ AnnotationConfiguration config = new AnnotationConfiguration(); config.addAnnotatedClass(User.class); SessionFactory factory= config.configure().buildSessionFactory(); Session session = factory.getCurrentSession(); session.beginTransaction(); try { User user= (User)session.load(User.class,new Long(1)); System.out.println(user.getPassword ()); } catch (ObjectNotFoundException e) { e.printStackTrace(); } session.getTransaction().commit(); /* System.out.println(user.getPassword()); This would fail!!! */ } public static void callGet() { AnnotationConfiguration config = new AnnotationConfiguration(); config.addAnnotatedClass(User.class); SessionFactory factory= config.configure().buildSessionFactory(); Session session = factory.getCurrentSession(); session.beginTransaction(); User user= (User)session.get(User.class,new Long(1)); System.out.println(user.getPassword()); session.getTransaction().commit(); /* no problem!!!*/ System.out.println(user.getPassword ()); } }
When to use get? When to use load?
So, after comparing and contrasting the load and get methods, the natural question that arises is "when do I use the get, and when do I use loa d?" It's a good question.
For the most part, you'll probably use the get method most often in your code. If you ever want to use the JavaBean that you are retrieving from the database after the database transaction has been committed, you'll want to use the get method, and quite frankly, that tends to be most of the time. For example, if you load a User instance in a Servlet, and you want to pass that instance to a Java Server Page for display purposes, you'd need to use the get method, otherwise, you'd have a LazyInitializationException in your JSP.
On the other hand, if your goal is largely transactional, and you are only going to be accessing the JavaBean of interest within a single unit of work that will pretty much end once the transaction is committed, you'll want to use the load method. For that matter, if you want to ensure that the JavaBean of interest is completely in sync with the database when it is used, you'll want to be using the load method as well, as this will ensure the fields of the JavaBean are being loaded in from the database, and are not just being loaded from the memory of the Java Virtual Machine on which you are running.
Furthermore, the load method may be the method of choice if you know, and are absolutely sure, that the entity you are searching for exists in the database with the primary key you are providing. If you don't know for sure that an entity bearing your primary key exists in the database, you can use the get method, and check to see if the instance that gets returned from the method call returns null.
Hibernate's get and load methods tend to cause plenty of problems for new Hibernate developers. But with a good understanding of the differences between the two, you shouldn't have any problem avoiding LazyInitialization and ObjectNotFound Exceptions.