One of the challenges I've faced when using Hibernate is how to elegantly manage sessions and transactions. In the past I've seen a lot of redundant code simply because the programmer couldn't think of a way to handle all their sessions and transactions from a single place. Sometimes you just need to do an insert or a delete - other times you need to do several operations in a single transaction. Because of this, I've seen getSession() and session.close() and tx.commit() all over the place. This is not only inefficient and time-consuming to code, but can also lead to major issues if you forget to leave out one commit() or session.close(). Those kinds of problems often don't show up in unit testing and aren't discovered until the code is under heavy load in production.
I'd like to take credit for the Hibernate framework I've been using lately, but the original concept was shown to me by the brilliant Matt Morrissette when he was working with me as a consultant. It consists of a fairly typical HibernateUtil class (which should be familiar to Hibernate users) and a couple of interfaces that represent actions. It follows the Executor design pattern, in which actions are passed to an executeAction() method of HibernateUtil. This one method handles all session and transaction management, and gracefully catches any exceptions and takes the appropriate action.
See the source of executeAction() after the jump...
/**
* Executor pattern - actions are passed into this method to be executed.
* If the action is a TransactedAction, the action will be encapsulated
* in a transaction. The transaction will be rolled back if an exception
* is thrown while the action is being executed.
*
* @param action
* @return Whatever object is returned by the action
* @throws HibernateException all exceptions are wrapped in a HibernateException
*/
public static Object executeAction(SessionAction action) throws HibernateException {
Transaction tx = null;
try {
//open a session
final Session session = getSession();
//if the action requires a transaction, start one
if(action instanceof TransactedAction) {
tx = session.beginTransaction();
}
//execute the action
Object result = action.execute(session);
//if a transaction was started, commit it
if(tx != null) {
tx.commit();
}
//return the session to the pool
session.close();
return result;
} catch (Exception e) {
//gracefully log the error, roll back any transaction,
//then wrap the exception in a HibernateException
LOG.error(ExceptionUtils.getFullStackTrace(e));
if(tx != null) {
tx.rollback();
}
throw new HibernateException(e);
}
}
public interface SessionAction {
Object execute(Session session) throws Exception;
}
For actions requiring a transaction, the TransactedAction is a simple subinterface of SessionAction:
public interface TransactedAction extends SessionAction { }
I usually also create some simple pre-built action classes for common operations, such as InsertAction:
public class InsertAction implements TransactedAction {
private Object entity;
public InsertAction(Object entity) {
this.entity = entity;
}
@Override
public Object execute(Session session) throws Exception {
session.save(entity);
return entity;
}
}
But you don't have to create a concrete class for every unique set of operations. In the example below, we create an anonymous instance of TransactedAction to persist all the objects in a list using a single transaction. Note the lack of any session or transaction management.
//do all the inserts in a single tx
TransactedAction txaction = new TransactedAction() {
public Object execute(Session session) throws Exception {
//iterate over the objects and store them
for(PersistentObject obj : results) {
session.save(obj);
}
return null;
}
};
HibernateUtil.executeAction(txaction);
Here is an example of how you can create a generic action class that can query any entity:
public class FetchByIdAction implements SessionAction {
@SuppressWarnings("unchecked")
private Class clazz;
private Serializable id;
@SuppressWarnings("unchecked")
public FetchByIdAction(Class clazz, Serializable id) {
this.clazz = clazz;
this.id = id;
}
@Override
public Object execute(Session session) throws Exception {
return session.get(clazz, id);
}
}
This is how you would use it:
MyEntityClass entity = (MyEntityClass) HibernateUtil
.executeAction(new FetchByIdAction(MyEntityClass.class, rowId));
No comments:
Post a Comment