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 error, 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 load?" 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.
|