Database (or system) transaction boundaries are always necessary. No communication with the database can occur outside of a database transaction (this seems to confuse many developers who are used to the auto-commit mode). Always use clear transaction boundaries, even for read-only operations. Depending on your isolation level and database capabilities this might not be required but there is no downside if you always demarcate transactions explicitly. Certainly, a single database transaction is going to perform better than many small transactions, even for reading data.
A Hibernate application can run in non-managed (i.e. standalone, simple Web- or Swing applications) and managed J2EE environments. In a non-managed environment, Hibernate is usually responsible for its own database connection pool. The application developer has to manually set transaction boundaries, in other words, begin, commit, or rollback database transactions himself. A managed environment usually provides container-managed transactions (CMT), with the transaction assembly defined declaratively in deployment descriptors of EJB session beans, for example. Programmatic transaction demarcation is then no longer necessary.
However, it is often desirable to keep your persistence layer portable between non-managed
resource-local environments, and systems that can rely on JTA but use BMT instead of CMT.
In both cases you'd use programmatic transaction demarcation. Hibernate offers a wrapper
API called Transaction that translates into the native transaction system of
your deployment environment. This API is actually optional, but we strongly encourage its use
unless you are in a CMT session bean.
Usually, ending a Session involves four distinct phases:
flush the session
commit the transaction
close the session
handle exceptions
Flushing the session has been discussed earlier, we'll now have a closer look at transaction demarcation and exception handling in both managed- and non-managed environments.
If a Hibernate persistence layer runs in a non-managed environment, database connections are usually handled by simple (i.e. non-DataSource) connection pools from which Hibernate obtains connections as needed. The session/transaction handling idiom looks like this:
// Non-managed environment idiom
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
// do some work
...
tx.commit();
}
catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e; // or display error message
}
finally {
sess.close();
}
You don't have to flush() the Session explicitly -
the call to commit() automatically triggers the synchronization (depending
upon the FlushMode for the session.
A call to close() marks the end of a session. The main implication
of close() is that the JDBC connection will be relinquished by the
session. This Java code is portable and runs in both non-managed and JTA environments.
A much more flexible solution is Hibernate's built-in "current session" context management, as described earlier:
// Non-managed environment idiom with getCurrentSession()
try {
factory.getCurrentSession().beginTransaction();
// do some work
...
factory.getCurrentSession().getTransaction().commit();
}
catch (RuntimeException e) {
factory.getCurrentSession().getTransaction().rollback();
throw e; // or display error message
}
You will very likely never see these code snippets in a regular application;
fatal (system) exceptions should always be caught at the "top". In other words, the
code that executes Hibernate calls (in the persistence layer) and the code that handles
RuntimeException (and usually can only clean up and exit) are in
different layers. The current context management by Hibernate can significantly
simplify this design, as all you need is access to a SessionFactory.
Exception handling is discussed later in this chapter.
Note that you should select org.hibernate.transaction.JDBCTransactionFactory
(which is the default), and for the second example "thread" as your
hibernate.current_session_context_class.
If your persistence layer runs in an application server (e.g. behind EJB session beans), every datasource connection obtained by Hibernate will automatically be part of the global JTA transaction. You can also install a standalone JTA implementation and use it without EJB. Hibernate offers two strategies for JTA integration.
If you use bean-managed transactions (BMT) Hibernate will tell the application server to start
and end a BMT transaction if you use the Transaction API. So, the
transaction management code is identical to the non-managed environment.
// BMT idiom
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
// do some work
...
tx.commit();
}
catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e; // or display error message
}
finally {
sess.close();
}
If you want to use a transaction-bound Session, that is, the
getCurrentSession() functionality for easy context propagation,
you will have to use the JTA UserTransaction API directly:
// BMT idiom with getCurrentSession()
try {
UserTransaction tx = (UserTransaction)new InitialContext()
.lookup("java:comp/UserTransaction");
tx.begin();
// Do some work on Session bound to transaction
factory.getCurrentSession().load(...);
factory.getCurrentSession().persist(...);
tx.commit();
}
catch (RuntimeException e) {
tx.rollback();
throw e; // or display error message
}With CMT, transaction demarcation is done in session bean deployment descriptors, not programmatically, hence, the code is reduced to:
// CMT idiom Session sess = factory.getCurrentSession(); // do some work ...
In a CMT/EJB even rollback happens automatically, since an unhandled RuntimeException
thrown by a session bean method tells the container to set the global transaction to rollback.
This means you do not need to use the Hibernate Transaction API at
all with BMT or CMT, and you get automatic propagation of the "current" Session bound to the
transaction.
Note that you should choose org.hibernate.transaction.JTATransactionFactory
if you use JTA directly (BMT), and org.hibernate.transaction.CMTTransactionFactory
in a CMT session bean, when you configure Hibernate's transaction factory. Remember to also set
hibernate.transaction.manager_lookup_class. Furthermore, make sure
that your hibernate.current_session_context_class is either unset (backwards
compatibility), or set to "jta".
The getCurrentSession() operation has one downside in a JTA environment.
There is one caveat to the use of after_statement connection release
mode, which is then used by default. Due to a silly limitation of the JTA spec, it is not
possible for Hibernate to automatically clean up any unclosed ScrollableResults or
Iterator instances returned by scroll() or
iterate(). You must release the underlying database
cursor by calling ScrollableResults.close() or
Hibernate.close(Iterator) explicitly from a finally
block. (Of course, most applications can easily avoid using scroll() or
iterate() at all from the JTA or CMT code.)
If the Session throws an exception (including any
SQLException), you should immediately rollback the database
transaction, call Session.close() and discard the
Session instance. Certain methods of Session
will not leave the session in a consistent state. No
exception thrown by Hibernate can be treated as recoverable. Ensure that the
Session will be closed by calling close()
in a finally block.
The HibernateException, which wraps most of the errors that
can occur in a Hibernate persistence layer, is an unchecked exception (it wasn't
in older versions of Hibernate). In our opinion, we shouldn't force the application
developer to catch an unrecoverable exception at a low layer. In most systems, unchecked
and fatal exceptions are handled in one of the first frames of the method call
stack (i.e. in higher layers) and an error message is presented to the application
user (or some other appropriate action is taken). Note that Hibernate might also throw
other unchecked exceptions which are not a HibernateException. These
are, again, not recoverable and appropriate action should be taken.
Hibernate wraps SQLExceptions thrown while interacting with the database
in a JDBCException. In fact, Hibernate will attempt to convert the exception
into a more meaningful subclass of JDBCException. The underlying
SQLException is always available via JDBCException.getCause().
Hibernate converts the SQLException into an appropriate
JDBCException subclass using the SQLExceptionConverter
attached to the SessionFactory. By default, the
SQLExceptionConverter is defined by the configured dialect; however, it is
also possible to plug in a custom implementation (see the javadocs for the
SQLExceptionConverterFactory class for details). The standard
JDBCException subtypes are:
JDBCConnectionException - indicates an error
with the underlying JDBC communication.
SQLGrammarException - indicates a grammar
or syntax problem with the issued SQL.
ConstraintViolationException - indicates some
form of integrity constraint violation.
LockAcquisitionException - indicates an error
acquiring a lock level necessary to perform the requested operation.
GenericJDBCException - a generic exception
which did not fall into any of the other categories.
One extremely important feature provided by a managed environment like EJB
that is never provided for non-managed code is transaction timeout. Transaction
timeouts ensure that no misbehaving transaction can indefinitely tie up
resources while returning no response to the user. Outside a managed (JTA)
environment, Hibernate cannot fully provide this functionality. However,
Hibernate can at least control data access operations, ensuring that database
level deadlocks and queries with huge result sets are limited by a defined
timeout. In a managed environment, Hibernate can delegate transaction timeout
to JTA. This functionality is abstracted by the Hibernate
Transaction object.
Session sess = factory.openSession();
try {
//set transaction timeout to 3 seconds
sess.getTransaction().setTimeout(3);
sess.getTransaction().begin();
// do some work
...
sess.getTransaction().commit()
}
catch (RuntimeException e) {
sess.getTransaction().rollback();
throw e; // or display error message
}
finally {
sess.close();
}
Note that setTimeout() may not be called in a CMT bean,
where transaction timeouts must be defined declaratively.