1.2.6. Loading and storing objects
Finally, we can use Hibernate to load and store objects. We write an EventManager
class with a main()
method:
package events;
import org.hibernate.Session;
import java.util.Date;
import util.HibernateUtil;
public class EventManager {
public static void main(String[] args) {
EventManager mgr = new EventManager();
if (args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
HibernateUtil.getSessionFactory().close();
}
private void createAndStoreEvent(String title, Date theDate) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
session.save(theEvent);
session.getTransaction().commit();
}
}
We create a new Event
object, and hand it over to Hibernate. Hibernate now takes care of the SQL and executes INSERT
s on the database. Let's have a look at the Session
and Transaction
-handling code before we run this.
A Session
is a single unit of work. For now we'll keep things simple and assume a one-to-one granularity between a Hibernate Session
and a database transaction. To shield our code from the actual underlying transaction system (in this case plain JDBC, but it could also run with JTA) we use the Transaction
API that is available on the Hibernate Session
.
What does sessionFactory.getCurrentSession()
do? First, you can call it as many times and anywhere you like, once you get hold of your SessionFactory
(easy thanks to HibernateUtil
). The getCurrentSession()
method always returns the "current" unit of work. Remember that we switched the configuration option for this mechanism to "thread" in hibernate.cfg.xml
? Hence, the current unit of work is bound to the current Java thread that executes our application. However, this is not the full picture, you also have to consider scope, when a unit of work begins and when it ends.
A Session
begins when it is first needed, when the first call to getCurrentSession()
is made. It is then bound by Hibernate to the current thread. When the transaction ends, either through commit or rollback, Hibernate automatically unbinds the Session
from the thread and closes it for you. If you call getCurrentSession()
again, you get a new Session
and can start a new unit of work. This
thread-bound
programming model is the most popular way of using Hibernate, as it allows flexible layering of your code (transaction demarcation code can be separated from data access code, we'll do this later in this tutorial).
Related to the unit of work scope, should the Hibernate Session
be used to execute one or several database operations? The above example uses one Session
for one operation. This is pure coincidence, the example is just not complex enough to show any other approach. The scope of a Hibernate Session
is flexible but you should never design your application to use a new Hibernate Session
for
every
database operation. So even if you see it a few more times in the following (very trivial) examples, consider
session-per-operation
an anti-pattern. A real (web) application is shown later in this tutorial.
To run this first routine we have to add a callable target to the Ant build file:
<target name="run" depends="compile">
<java fork="true" classname="events.EventManager" classpathref="jboss_hibernate_libraries">
<classpath path="${targetdir}"/>
<arg value="${action}"/>
</java>
</target>
The value of the action
argument is set on the command line when calling the target:
C:\hibernateTutorial\>ant run -Daction=store
You should see, after compilation, Hibernate starting up and, depending on your configuration, lots of log output. At the end you will find the following line:
[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)
This is the INSERT
executed by Hibernate, the question marks represent JDBC bind parameters. To see the values bound as arguments, or to reduce the verbosity of the log, check your log4j.properties
.
Now we'd like to list stored events as well, so we add an option to the main method:
if (args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
else if (args[0].equals("list")) {
List events = mgr.listEvents();
for (int i = 0; i < events.size(); i++) {
Event theEvent = (Event) events.get(i);
System.out.println("Event: " + theEvent.getTitle() +
" Time: " + theEvent.getDate());
}
}
We also add a new listEvents() method
:
private List listEvents() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
List result = session.createQuery("from Event").list();
session.getTransaction().commit();
return result;
}
What we do here is use an HQL (Hibernate Query Language) query to load all existing Event
objects from the database. Hibernate will generate the appropriate SQL, send it to the database and populate Event
objects with the data. You can create more complex queries with HQL, of course.
Now, to execute and test all of this, follow these steps:
-
Run ant run -Daction=store
to store something into the database and, of course, to generate the database schema before through hbm2ddl.
-
Now disable hbm2ddl by commenting out the property in your hibernate.cfg.xml
file. Usually you only leave it turned on in continous unit testing, but another run of hbm2ddl would
drop
everything you have stored - the create
configuration setting actually translates into "drop all tables from the schema, then re-create all tables, when the SessionFactory is build".
If you now call Ant with -Daction=list
, you should see the events you have stored so far. You can of course also call the store
action a few times more.
Note: Most new Hibernate users fail at this point and we see questions about
Table not found
error messages regularly. However, if you follow the steps outlined above you will not have this problem, as hbm2ddl creates the database schema on the first run, and subsequent application restarts will use this schema. If you change the mapping and/or database schema, you have to re-enable hbm2ddl once again.