In order to illustrate the process of designing we will take a use case as underlying requirement.
We have two kinds of products: paper books and eBooks. Paper books are taken from stock, when they are ordered by the customer. Ebooks are PDF documents, which can be provided immediately to the customer. There is not stock available.
A customer orders a book. The system checks the stock. If the book is available it is delivered and the stock is reduced. If it is not available, the order remains undelivered. A use case which issues a command to a supplier will not be treated here.
The question is how to handle exceptions happening during the access of Hibernate and where the transaction demarcations can be found.
I would propose the following transaction demarcations:
The first transaction is around the initial creation of an order. The later change of the order status should have no influence on the order creation.
The process of checking the stock level, the reduction of the stock and the update should happen in one transaction.
To protect the application against parallel access we should consider to lock the stock level of the product. If we do not do this, we might have a situation with two clients doing the following:
The exceptions should not be shown to the customer but handled in a proper way.
A customer orders a book. The order is created and the applications sends the eBook as PDF to the customer. The order status is set to delivered. We will keep things simple and allow only the order of one book which is a paper or an eBook. Where are the transaction demarcations and how will we treat exceptions?
I propose the following transaction demarcations:
The first transaction is around the initial creation of an order. The later change of the order status should have no influence on the order creation.
The update of the order status should only happen when the PDF has been successfully sent. This is our second transaction.
The exceptions should not be shown to the customer but handled in a proper way.
We have defined our use cases. The next step is to set up a proper structure for our application.
Choosing a suitable structure is a question of personal preferences. I propose the following approach:
We will set up a business logic layer containing the use cases. (OrderManger in the figure below) This layer is responsible for the transaction management as well. This layer uses DAOs to access the data. A DAO does not manage any transactions at all. It receives a Hibernate session and just updates or returns data.
The Problem of reusing business logic
There is one situation we must think about. The class OrderManager will have a method called processOrder which will manage all the transactions. What will happen if we want to use this method in a larger context with its own transaction? For example another class called ExportManager verifies if we need an export permission. This class starts a transaction itself, requests the permission and continues to processOrder of our class OrderManager. In this case the OrderManager should not create a transaction of its own but should run in the transaction of our ExportManager.
Once approach to solve this problem is to improve our processOrder method. We can make it check for an existing transaction context. If one exist the method does not handle the transaction, else it does.
public void processOrder(Order order) { // check if this method is responsibe for the transaction boolean handleTransaction = true; if (getSession().getTransaction().isActive()) handleTransaction = false; if (handleTransaction) getSession().beginTransaction(); // do something important here if (handleTransaction) getSession().getTransaction().commit(); }
Approach for reuse of business logic
Our Business methods should be clever enough to check if they run within a transaction. If this is allowed, they should not open a new transaction. If it is not allowed they should throw an exception.
Those using EJB 2 or container managed transaction (CMT) will know the different transaction types we are simulating here.
CMT supports a wider range of transactions like
Requires-new: to have a new transaction for this method, open transactions are paused.
Requires: will run in an existing transaction if exist. If there is no transaction a new one will be started.
But this is not our topic here. I just wanted to mention it.
Reattaching problems
A use case frequently reattaches objects and updates them. Most reattachment strategies do not allow that an object is already attached. If you allow that use cases can call other use cases you must be careful that you do not try to reattach an object which is already in the session.
Simple use cases
Let’s think about simple use cases that happen very often in applications:
The methods are already defined in the ArticleDao. The ArticleManager would begin the transaction, call the Dao and close the transaction. We would need a method in the business class only adding the transaction.
Alternatively we could add transaction handling to the DAO methods. When it is not called within an transaction, the DAO saves the information that is responsible for the transaction and starts and commits the transaction. In this case the DAO must be clever enough to start a new transaction itself. The following method could be used in a DAO. It ensures that a transaction is used.
private boolean transactionHandled; protected void ensureTransaction() { if (!transactionHandled) { if (getSession() == null || !getSession().getTransaction().isActive()) { transactionHandled = true; // get a current or new session, we are responsible so we must // be aware that the session could not exist. factory.getCurrentSession().beginTransaction(); } } else { transactionHandled = false; try { getSession().getTransaction().commit(); getSession().close(); // only useful when auto close in hibernate.cfg.xml is disabled } catch (HibernateException e) { // do not print stacktrace as we will rethow the exception // e.printStackTrace(); // clean up the session and transaction when an exception // happens try { getSession().getTransaction().rollback(); getSession().close();// only useful when auto close in hibernate.cfg.xml is disabled } catch (HibernateException e1) { } if (e instanceof StaleObjectStateException) // normaly a // version // problem throw e; else throw new HibernateException(e); } } }
When we use version tracking a StaleObjectException can happen. The method above will rethrow the exception to allow that our web action can handle this information.
There are also reasons not to use this kind of “clever” DAO. First, the structure is not very consistent. Your DAOs become some kind of business objects. You will need to handle Hibernate exceptions directly in your web layer.
I wanted to show you both approaches. I think that not using “clever” DAOs is by far a cleaner approach but using them leads to less redundant work.
A DAO should not have any code configuring a specific session, but it should get the session or a session factory from the outside. The reason is that you reduce relations and dependencies in your code and get an application that is easier to maintain or debug. You could even think of a situation where you have multiple databases and a DAO should receive the session of a specific database.
As a consequence you will need a DAOFactory to create the DAO classes. This factory initialises the DAO object with a session or a session factory.
One issue I do differently compared to Spring/Hibernate’s examples or the CaveatEmptor application from Hibernate in Action, is that I do not set a session in a DAO but only a sessionFactory. The reason becomes clear when you look at the following case.
OrderDao orderDao = DaoFactory.getOrderDao(); deliverEbooks(order); deliverPaperBook(order); getSession().beginTransaction(); // here is the problem orderDao.reattach(order);
If we initialise the orderDao with a session there is a fair chance that a deliverEbooks method is closing the current session. We started a new session with the call to getSession() but the orderDao still has the old abandoned session. This is why I prefer to save only a session factory in the DAO that returns always the session currently open.
As one example you may use the project DaoExample.
Take a look at the source code now and research the following things
Dao Creation
The class OrderDao extends the class BasicDaoImp. Common methods like save, update, lock, delete are implemented in the BasicDaoImp. OrderDao just adds some special methods needed to access an order. The orderDao is created by the DaoFactory.
... public T findById(Integer id) { return (T) getSession().get(type, id); } public void reattach(T entity) { getSession().buildLockRequest(LockOptions.READ).lock(entity); } public void save(T entity) { getSession().saveOrUpdate(entity); } public void update(T entity) { getSession().update(entity); } public class ArticleDaoImp extends BasicDaoImp implements ArticleDao { public ArticleDaoImp(SessionFactory factory) { super(factory, Article.class); } public boolean lockAndDecrease(Article article, int quantity) { getSession().buildLockRequest(LockOptions.UPGRADE).lock(article); return article.decreaseStock(quantity); } }