Okay, assuming you're following allong in
these tutorials, I'm just over the moon that we've got a basic
Hibernate environment configured, we've got some code compiled, and
we're successfully saving data to our database. Plus, we've
effectively used a few JPA annotations in our code as well. All in
all, were making good progress, but there's still a long way to go
before we can say that we've mastered Hibernate.
This tutorial will go beyond the basic
save functionality we saw in the previous tutorial,, and take a look
at all four of the basic CRUD operations, namely:
- Create Operations (a quick review of the previous tutorial)
- Retrieve Operations
- Updating Functionality
- Delete and Destroy Functionality
Interestingly enough, on the topic of
data retrieval, there are actually a few different ways that entities
can be brought into your application given a unique primary key. This
chapter will finish off by discussing these entity acquisition
options, as we try and shed some light on their differences and
benefits.
Create (We've Seen This Before)
All database driven
applications revolve around the four basic CRUD operations: Create,
Read (retrieve?), Update and Delete (destroy?). In
previous chapters, we examined the code that was required to get the
Hibernate framework to create a new database record based on an
instance of the User class. We're going to start off this chapter by
leveraging that knowledge of Hibernate as we create a new class called
CrudRunner.
Take a look at the starter code for the
CrudRunner below. As you can see, the CrudRunner is a simple Java
class with a static create method and a runnable main method, that for
now, simply calls the create method. As far as the create method goes,
there's nothing new in there. It's the same Hibernate code we saw in
the previous tutorial when we learned how to save the state of a User
instance to the database.
All That Plumbing Code
All of the plumbing code that has to do
with creating a Hibernate AnnotationConfiguration object, which is
needed to create a SessionFactory, which is eventually used to
generate a Hibernate Session, is very verbosely coded in the
CrudRunner class. We're going to repeat that stuff ad nauseum in this
chapter. Of course, we could always factor it out into a single,
reusable method, but we'll save that for another time and place. For
now, it's probably a good learning strategy to be further exposed to
the fundamental code that's required to get Hibernate to work.
The CrudRunner Class: First Iteration
package com.examscam;
/* Notice the com.examscam package!!! */
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import com.examscam.model.User;
import java.util.*;
public class CrudRunner {
public static void main(String[] args) {
/* Run all the static methods (currently only 1) */
CrudRunner.create();
}
public static void create (){
/* Create the config object, reading from the
hibernate.cfg.xml file. */
AnnotationConfiguration config =
new AnnotationConfiguration();
/* Make sure all annotated classes
are added to the configuration */
config.addAnnotatedClass(User.class);
SessionFactory factory;
/* Obtain the SessionFactory after calling
the config() method of the A
nnotationConfiguration instance. */
factory =
config.configure().buildSessionFactory();
/* Get a Hibernate Session */
Session session = factory.getCurrentSession();
session.beginTransaction();
/* Create and initialize an instance
of a JPA annotated class */
User user = new User();
user.setPassword("abc123");
/* Have the instance touch the session and then
commit the transaction */
session.save(user);
session.getTransaction().commit(); }
}
Retrieving and Querying Data with
Hibernate
So, we should be pretty solid on creating
a new record in the database using Hibernate, seeing that we've done
it a few times. The next question is: how do we query and see all of
that fabulous information that we have previously stuffed inside of
our database?
Well, querying a database with Hibernate
is a little bit more of an involved process than simply creating a new
record. I mean, you have to create a special query object, you have to
pass some special Hibernate 'SQL' to the createQuery method of the
Hibernate session, you have to throw the results of your query into a
java.util.List object, and then, after sprinkling in a little Haitian
Voodoo, you do a few casts and subsequently inspect the data that
Hibernate throws back at you. As I said, querying the database is a
slightly more involved process than simply saving the state of a JPA
annotated POJO, but don't be intimidated. In the grand scheme of
things, pulling information out of a database using Hibernate really
isn't all that difficult.
I'm going to add a new static method to
my CrudRunner class called retrieve that has all the code in it
that's needed to pull all of the records out of the user database
table. Take a look at it; we'll debrief it in a moment.
public static void retrieve() {
AnnotationConfiguration config =
new AnnotationConfiguration();
config.addAnnotatedClass(User.class);
SessionFactory factory;
factory = config.configure().buildSessionFactory();
Session session = factory.getCurrentSession();
session.beginTransaction();
java.util.List allUsers;
Query queryResult=session.createQuery("from User");
allUsers = queryResult.list();
for (int i = 0; i < allUsers.size(); i++) {
User user = (User) allUsers.get(i);
System.out.println(user.getPassword());
}
session.getTransaction().commit();
}
Debriefing the retrieve() Method
The retrieve method starts off with some
redundant and repetitive Hibernate plumbing code that basically
initializes the Hibernate infrastructure classes and gets our Java
program ready to interact with the database. Again, you will notice
that a Hibernate Session is obtained from the SessionFactory, which
itself is built from Hibernate's AnnotationConfiguration object. This
Hibernate Session then begins a transaction, which is something we
should do every time we interact with our persistence store. Of
course, we've seen all of this plumbing code umpteen times before.
Don't worry, we'll eventually look at factoring this repetitive code
out into a helper method or HibernateUtil class or something, but for
now, we'll leave it in, just for the sake of simplicity.
/* your standard Hibernate connection code */
AnnotationConfiguration config =
new AnnotationConfiguration();
config.addAnnotatedClass(User.class);
SessionFactory factory;
factory = config.configure().buildSessionFactory();
Session session = factory.getCurrentSession();
session.beginTransaction();
Querying the Database
After the Hibernate session is obtained,
we declare an object of type java.util.List, to which we assign the
variable name allUsers. Basically, when we do a standard, non-unique
query of the database, we can expect a result to be returned to us in
the form of a java.util.List. The allUsers list will hold the results
of our from User query.
java.util.List allUsers;
Say Hello to HQL
Once we have our List for holding the
query results, and this is a pretty important point, the createQuery
method is invoked on the Hibernate session, with the String "from
User" being passed in as the argument.
/* specifically the org.hibernate.Query */
Query queryResult = session.createQuery("from User");
allUsers = queryResult.list();
As you have probably guessed, the literal
String "from User", which is passed into the Hibernate
Session's createQuery method, is a special type of query statement
that is derived from a Hibernate specific query language, HQL. For
now, the "from User" String can be thought of as the object
oriented equivalent of SELECT * FROM USER.
Query queryResult=session.createQuery("from User");
allUsers = queryResult.list();
However, don't be fooled by what may
appear on the surface to be a great similarity between standard SQL
and the query language used by Hibernate. The Hibernate Query
Language, HQL, is actually an object-oriented data query language,
meaning that HQL understands object oriented concepts such as
association, inheritance, aggregation and polymorphism. HQL is pretty
awesome once you get into it, but it is definitely different from SQL.
Execution of the createQuery("from
User") method call returns a Hibernate object called a Query.
Calling the list() or iterate() method of the Hibernate Query object
will trigger a database call that will execute the given query, and
return a collection of JavaBeans that match the type and criteria of
the HQL statement. As you could imagine, the list() method of the
Query object returns a java.util.List of JavaBeans, whereas the
iterate() method returns an object the implements the
java.util.Iterator interface.
allUsers = queryResult.list();
Looping through the List
So, when we invoke the list() method on
the Query instance, we get a java.util.List in return. I guess the big
question is then: what exactly does this java.util.List contain?
Well, it contains instances of the User
class, representing the data in the underlying user table of the
database.
A Subtle Distinction
Now, since the POJO and the database
table are both named User, an important little subtlety may have been
missed. When the HQL statement references the User, it is actually
referencing the Java class named User, not the database table named
user. This is a subtle but extremely important distinction. You see,
we could change the name and location of the underlying database table
and infrastructure, but so long as our User class is mapped accurately
to the underlying datastore, the HQL query doesn't have to change. You
see, our HQL queries are based on our Java classes, and Hibernate
translates those HQL queries into the appropriate SQL needed to
manipulate the underlying database. Of course, Hibernate does all of
those translations behind the scenes, effectively insulating Java
developers from any changes that might happen to the underlying
database. Awesome, eh?
Looping through the Query Results
Looping through the list, and extracting
a User instance out of the List, with a combination of the List's
get(int) method and a handy cast, gives us access to all of the User
instances returned by the query.
for (int i = 0; i < allUsers.size(); i++) {
User user = (User) allUsers.get(i);
System.out.println(user.password());
}
The CrudRunner's retrieve() Method
Once you have a User instance both
extracted from the List and cast into the appropriate type, it is
completely up to the you with regards to what you want to do with it.
In this example, we simply print out the password of the user instance
to the console, which, given the fact that I'm looping through every
instance in the List, will provide me the passwords of all of the
entities in the user table of the database.
And that's pretty much it! To retrieve
data from the database, you simply leverage the Hibernate Session's
createQuery method, pass in some HQL, convert the guts of the returned
Query object into an easy to use List, and then loop through the
collection of POJOs contained in the List. It's just that easy!
public static void retrieve() {
/* your standard Hibernate connection code */
AnnotationConfiguration config =
new AnnotationConfiguration();
config.addAnnotatedClass(User.class);
SessionFactory factory;
factory = config.configure().buildSessionFactory();
Session session = factory.getCurrentSession();
session.beginTransaction();
System.out.println("Querying the whole database...");
java.util.List allUsers;
Query queryResult=session.createQuery("from User");
allUsers = queryResult.list();
System.out.println("Number of rows: " + allUsers.size());
for (int i = 0; i < allUsers.size(); i++) {
User user = (User) allUsers.get(i);
System.out.println(user.getPassword());
}
System.out.println("Database contents delivered...");
session.getTransaction().commit();
}
Retrieving a Unique Entity with
Hibernate
The retrieve method we just coded dropped
a pretty heavy hammer on our database, returning all of the entities
in the underlying system via the from User HQL query. However,
not all queries are interested in obtaining a massive resultset from
the database. Quite often, database queries should return either zero
or one unique result from the database. Let's explore how we can
handle the scenario where a query should, at most, return one, single,
unique result from the database.
The primary key of the User, named id, is
a unique value that can't be duplicated in the user table. If we were
to query the user table on the id alone, we should never receive more
than one record in return. So, let's do an HQL query that looks for a
User, based on a primary key of a User already added to your database.
I'm going to use the id of 1.
A Note About Primary Keys
The retrieveFromId method we are about to
create will use HQL and something called variable injection to
retrieve a unique entity from the database. The real objective of this
method is to explore HQL a little bit deeper, and demonstrate how to
handle queries that will only return one or zero results.
However, it should be noted that the
Hibernate Session provides two very helpful methods for retrieving a
single entity from a database if you know the primary key for the
record, with those methods being load() and get(). We'll track those
methods down at the end of this tutorial. I just wanted to mention
that, if you do have a primary key for an entity, there are
other, potentially easier ways to retrieve the associated instance
with Hibernate.
Variable Injection Queries: Hibernate
vs. SQL
When performing a query where the
criteria isn't exactly known until runtime, we need to take advantage
of HQL's variable injection facilities. If you've ever used JDBC and
worked with PreparedStatements, you'll notice a number of similarities
in the philosophy of how variables are injected into HQL statements,
although the syntax between HQL and JDBC statements may be a tiny bit
different.
With a JDBC PreparedStatement, a select
query against the user table, where the value of the id is not known
until runtime, would look something like this:
String idVariable;
/*obtain the id from user somehow!!!*/
String sqlQuery = "select * from user where id = ?";
PreparedStatement ps = con.prepareStatement(sqlQuery);
/*replace the first ?
mark with the value held by the variable*/
ps.setString(1, idVariable);
ResultSet rs = ps.executeQuery();
Notice the SQL code of select *
from user where id = ?, with an emphasis on the question mark. When
this code is run, the value of the id is not known until runtime, so
it must be garnered through the user, or perhaps, from some other part
of the program. So, instead of hard coding in a value for the id, you
just code the SQL statement with a question mark, ?, in the spot where
the unknown value should go. You then code some way to get this value
at runtime, substituting the initialized runtime variable for the
question mark in the query using one of the methods of the JDBC
PreparedStatement object. Once the ? is initialized with a real value,
you send the query to the database. That's how JDBC PreparedStatements
work. Hibernate is similar, but different. Let's take a look at how
Hibernate would achieve the same result.
Variable Injection with Hibernate
Hibernate uses a syntax that is very
similar to that of a JDBC PreparedStatement to perform runtime
variable injection with an HQL statement. But while the syntax may be
a bit different, the idea is pretty much the same.
Rather than putting a question mark into
the Hibernate Query Language String where the runtime variable needs
to be inserted, HQL statements typically use a variable name with a
preceding colon. This is actually a bit of an improvement over using
the old question mark, because you can use real names for where the
variables need to be inserted, rather than rhyming off the question
marks as you typically do with JDBC PreparedStatements. Here's how our
Hibernate query String might look when leveraging the facilities of
variable injection:
String queryString = "from User
where id = :id";
This HQL String is then passed to the
createQuery method of the Hibernate Session to create a Query object:
Query query =
session.createQuery(queryString);
From there, a setter method on the
initialized query object, which I creatively named query, is used to
substitute the variable, defined in the HQL String as :id, with an
actual value. Here, we use an id value passed into the program:
query.setInteger("id",
idVariable);
Once the placeholder in the HQL query has
been substituted by our intended variable, we can execute the query:
Object queryResult = query.uniqueResult();
User user = (User)queryResult;
Once the query is executed, we take the
queryResult, which is returned as an Object, and cast it into a User.
Once the cast is completed, it's totally up to you what to do with the
instance. It's all just that easy!
Returning a Unique Result
A notable difference between the
Hibernate code used to obtain a unique result, as opposed to a query
that returns multiple rows, is the name of the method invoked on the
Hibernate Query object. When you expect a single row to be returned
from the database, you invoke the query object's uniqueResult()
method. When you expect multiple rows to be returned, you use the
Query's list() method.
Object queryResult = query.uniqueResult();
User user = (User)queryResult;
Let's add a static retieveFromId
method to the CrudRunner class that leverages all of our newfound
knowledge on the topic of variable injection. The full method that
returns a single User instance, given a primary key provided by the
calling program, is as follows (note that a primary key of 1 is
hard coded into the main method.):
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;
}
public static void main(String[] args) {
CrudRunner.create();
CrudRunner.retrieve();
CrudRunner.retrieveFromId(1); /* id of 1 */
}
Updating Database Records with
Hibernate
So far, we've mastered the 'C' and 'R' of
the CRUD acronym. Okay, maybe saying that we've mastered them is going
a bit far, but we've been exposed to them, and we're getting a good
idea of how they work. The next letter we have to tackle is the 'U,'
or the update operation.
The process of updating a record is
fairly simple. All you have to do is get a User object, update some of
its information, and then pass that updated User POJO to the Hibernate
Session.
For my little example, I'm going to
update all of the passwords in the User table, changing all existing
passwords to the word 'password.' So, to do that, I'll first retrieve
all of the records from the database in the form of a java.util.List,
using the techniques that were mastered when dealing with the retrieve
operation. I will then pull the User objects out of the
java.util.List, one at a time, and call the setter on the User POJO,
updating the password of the User instance to the word 'password.'
/* update an instance of the User class */
user.setPassword("password");
After updating the User instance, I'll
then pass the updated instance to the update method of the Hibernate
Session.
/* pass the updated user to the
session's update method*/
session.update(user);
Of course, nothing is guaranteed to be
updated until the transaction is committed, so, once all of the User
objects in the java.util.List are updated, the
getTransaction().commit() method is invoked on the Hibernate Session.
session.getTransaction().commit();
The Updated CrudRunner
Here's the CrudRunner class with the
updateAll method contained within. The meat of the create and retrieve
methods have been scratched out for the sake of brevity.
public class CrudRunner {
public static void main(String[] args) {
CrudRunner.create();
CrudRunner.retrieve();
CrudRunner.retrieveFromId(1);
CrudRunner.updateAll();
}
public static void create(){ }
public static void retrieve(){ }
public static User retrieveFromId(int idValue) { }
public static void updateAll() {
AnnotationConfiguration config =
new AnnotationConfiguration();
config.addAnnotatedClass(User.class);
SessionFactory factory;
factory = config.configure().buildSessionFactory();
Session session = factory.getCurrentSession();
session.beginTransaction();
java.util.List allUsers;
System.out.println("Updating all records...");
Query queryResult = session.createQuery("from User");
allUsers = queryResult.list();
System.out.println("# of rows:"+allUsers.size());
for (int i = 0; i < allUsers.size(); i++) {
User user = (User) allUsers.get(i);
System.out.println(user);
user.setPassword("password");
session.update(user);
}
System.out.println("Database table updated...");
session.getTransaction().commit();
}
}
save, update & saveOrUpdate
In an earlier tutorial, when we saved a
User instance to the database, we used the saveOrUpdate method.
When we tackled the create method of the CrudRunner, we used save.
Now that we are updating instances, we are using the update
method. It's all kinda confusing, and people often wonder which one of
the three methods they should use. Basically, when you have an entity
that does not have a primary key, and you know that it needs to be
saved as a new record to the database, you can use the save() method.
A nice thing about the save method is that it returns the primary key
generated as the new instance is saved.
public Serializable save(Object object)
throws HibernateException
Persist the given transient
instance, first assigning a generated identifier. (Or using the
current value of the identifier property if the assigned generator is
used.) -Hibernate API JavaDoc
On the other hand, the update() method
will do just that, update the database record that is associated with
the entity being passed to the method. The method returns void, throws
the HibernateException, and expects the entity passed in to
have a primary key associated with it.
public void update(Object object)
throws HibernateException
Update the persistent
instance with the identifier of the given detached instance. If there
is a persistent instance with the same identifier, an exception is
thrown. -Hibernate API JavaDoc
Finally, the saveOrUpdate() method
intelligently combines the save and update functionality, knowing to
create a new record for an instance passed in that does not
have a primary key, and updating the associated record if the instance
passed in does have a primary key associated with it.
public void saveOrUpdate(Object object)
throws HibernateException
Either save(Object) or
update(Object) the given instance, depending upon resolution of the
unsaved-value checks (see the manual for discussion of unsaved-value
checking). -Hibernate API JavaDoc
How to Delete A Record with Hibernate
The final function associated with the
acronym CRUD is delete, or destroy; whichever term you prefer.
Deleting a record using the Hibernate framework is, in many respects,
remarkably similar to the coding of an update, with the exception of
the fact that the word update gets transposed with the word delete.
Once we obtain the Hibernate Session, all
we need to do to delete a record is pass an object of the appropriate
type to the Session's delete method. Of course, for this example, the
appropriate type is an instance of the User class. The only really
important piece of information the User instance needs to contain is
the primary key of the record to be deleted. Once a valid User
instance, with an appropriate primary key, is passed to the Hibernate
Session's delete method, and a transaction commit is issued, the
corresponding record is deleted from the database.
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setId(2); /*won't work if setId
is a private method*/
session.delete(user);
session.getTransaction().commit();
Looking at the code above, a delete
method that hard codes an id into an instance of a User (which
actually shouldn't be possible, because the setId(int) method of the
User class should be private) isn't a good idea. Actually, arbitrarily
picking a primary key out of thin air is a very, very bad idea. But,
if a user record with a primary key of 2 does in fact exist within the
database, then after the session.getTransaction().commit() method is
invoked, the corresponding record in the database would be deleted.
Again, persistent objects should
initially be pulled from the database when you want to delete them, as
opposed to just arbitrarily picking a primary key out of thin air. A
better example might be the following deleteAll method, which looks up
all the entities in the user table, and then quite destructively, one
at a time, deletes every record in the database. A violent method for
sure, but extreme violence is justifiable if it helps us learn.
Deleting Every Database Record with
Hibernate
Our CrudRunner has an updateAll method,
so why not add a deleteAll method as well? The syntax of the deleteAll
method is very similar to that of the updateAll, with the exception of
the fact that we call the delete method on the Hibernate session, as
opposed to the update method.
public static void deleteAll() {
AnnotationConfiguration config =
new AnnotationConfiguration();
config.addAnnotatedClass(User.class);
SessionFactory factory;
factory = config.configure().buildSessionFactory();
Session session = factory.getCurrentSession();
session.beginTransaction();
java.util.List allUsers;
System.out.println("Deleting all records...");
Query queryResult = session.createQuery("from User");
allUsers = queryResult.list();
for (int i = 0; i < allUsers.size(); i++) {
User user = (User) allUsers.get(i);
System.out.println(user);
session.delete(user);
}
System.out.println("Database contents deleted...");
session.getTransaction().commit();
}
Again, this method is very similar to the
updateAll() method, where we use the Hibernate Session, the
createQuery method, and some HQL in the form of the select String
"from User". Once the List of query results are returned to
us, we hold the results in a java.util.List, and then loop through the
List, one entity at a time, extracting our User POJOs out of the list,
and issuing a session.delete(user); invocation. Finally, when the
looping is complete, the session.getTransaction().commit() method is
issued, and our User database table is empty, because we have deleted
everything in it. It's just that easy.
The Fabulous, Full, CrudRunner Class:
package com.examscam;
import java.util.List; import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.Query;import org.hibernate.Session;
import org.hibernate.SessionFactory; import com.examscam.model.*;
public class CrudRunner {
public static void main(String[] args) {
CrudRunner.create();CrudRunner.retrieve();CrudRunner.deleteAll();
CrudRunner.retrieveFromId(1);CrudRunner.updateAll();
}
public static void create() {
AnnotationConfiguration config = new AnnotationConfiguration();
config.addAnnotatedClass(User.class);
SessionFactory factory= config.configure().buildSessionFactory();
Session session = factory.getCurrentSession();
session.beginTransaction();
User user = new User();user.setPassword("abc123");
session.save(user);
session.getTransaction().commit();
}
public static void retrieve() {
AnnotationConfiguration config = new AnnotationConfiguration();
config.addAnnotatedClass(User.class);
SessionFactory factory= config.configure().buildSessionFactory();
Session session = factory.getCurrentSession();
session.beginTransaction();
Query queryResult = session.createQuery("from User");
java.util.List allUsers;
allUsers = queryResult.list();
for (int i = 0; i < allUsers.size(); i++) {
User user = (User) allUsers.get(i);
}
session.getTransaction().commit();
}
public static User retrieveFromId(int idValue) {
AnnotationConfiguration config = new AnnotationConfiguration();
config.addAnnotatedClass(User.class);
SessionFactory factory= config.configure().buildSessionFactory();
Session session = factory.getCurrentSession();
session.beginTransaction();/*lets hope an id of 1 exists!*/
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();
return user;
}
public static void updateAll() {
AnnotationConfiguration config =
new AnnotationConfiguration();
config.addAnnotatedClass(User.class);
SessionFactory factory=
config.configure().buildSessionFactory();
Session session = factory.getCurrentSession();
session.beginTransaction();
List allUsers;
System.out.println("Updating all records...");
Query queryResult = session.createQuery("from User");
allUsers = queryResult.list();
System.out.println("# of rows: "+ allUsers.size());
for (int i = 0; i < allUsers.size(); i++) {
User user = (User) allUsers.get(i);
System.out.println(user);
user.setPassword("password");
session.update(user);
}
System.out.println("Database contents updated...");
session.getTransaction().commit();
}
public static void deleteAll() {
AnnotationConfiguration config = new AnnotationConfiguration();
config.addAnnotatedClass(User.class);
SessionFactory factory=config.configure().buildSessionFactory();
Session session = factory.getCurrentSession();
List allUsers;
session.beginTransaction();
Query queryResult = session.createQuery("from User");
allUsers = queryResult.list();
for (int i = 0; i < allUsers.size(); i++) {
User user = (User) allUsers.get(i);
System.out.println(user);
session.delete(user);
}
session.getTransaction().commit();
}
}
Loading Entities with Hibernate
Now, earlier in this 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.
|