I'm developing a simple "Book Store" project using Struts 1.3 + JPA (with Hibernate as persistence provider). I cannot switch to Spring or any other more sophisticated development environment (e.g., Jboss) and I cannot use any Hibernate-specific technique (e.g., Session
class).
Given the fact that I'm in a JSE Environment, I need to explicitly manage the whole EntityManager's lifecycle.
The Book
entity is defined as follows:
@Entity
public class Book {
@Id private String isbn;
private String title;
private Date publishDate;
// Getters and Setters
}
I defined three Action
classes, which are responsible, respectively, of retrieving all book instances, retrieving a single book instance by its ISBN and merging a detached book into the DB.
In order to increase separation of concerns between business-logic code and data-access code, I introduced a simple BookDAO
object, which is charge of executing CRUD operations. Ideally, all data-access related calls should be delegated to the persistence layer. For example, the ListBookAction
is defined as follows:
public class ListBookAction extends Action {
private BookDAO dao = new BookDAO();
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
// Retrieve all the books
List<Book> books = dao.findAll();
// Save the result set
request.setAttribute("books", books);
// Forward to the view
return mapping.findForward("booklist");
}
}
The BookDAO object needs to access an EntityManager
instance in order to do any operation. Given that EntityManger
is not thread-safe, I introduced an helper class named BookUnitSession
which encapsulates EntityManager within a ThreadLocal
variable:
public class BookUnitSession {
private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("BookStoreUnit");
private static final ThreadLocal<EntityManager> tl = new ThreadLocal<EntityManager>();
public static EntityManager getEntityManager() {
EntityManager em = tl.get();
if (em == null) {
em = emf.createEntityManager();
tl.set(em);
}
return em;
}
}
Everything seems to work, but I still have some concerns. Namely:
Thanks
During the last few days I designed a possible solution. What I was trying to construct with the BookUnitSession
class was actually the EntityManagerHelper
class:
public class EntityManagerHelper {
private static final EntityManagerFactory emf;
private static final ThreadLocal<EntityManager> threadLocal;
static {
emf = Persistence.createEntityManagerFactory("BookStoreUnit");
threadLocal = new ThreadLocal<EntityManager>();
}
public static EntityManager getEntityManager() {
EntityManager em = threadLocal.get();
if (em == null) {
em = emf.createEntityManager();
threadLocal.set(em);
}
return em;
}
public static void closeEntityManager() {
EntityManager em = threadLocal.get();
if (em != null) {
em.close();
threadLocal.set(null);
}
}
public static void closeEntityManagerFactory() {
emf.close();
}
public static void beginTransaction() {
getEntityManager().getTransaction().begin();
}
public static void rollback() {
getEntityManager().getTransaction().rollback();
}
public static void commit() {
getEntityManager().getTransaction().commit();
}
}
Such a class ensures that each thread (i.e., each request) will get its own EntityManager
instance. Consequently, each DAO object can obtain the correct EntityManager
instance by calling EntityManagerHelper.getEntityManager()
According to the session-per-request pattern each request must open and close its own EntityManager
instance, which will be in charge of encapsulating the required unit of work within a transaction. This can be done by means of an intercepting filter implemented as a ServletFilter
:
public class EntityManagerInterceptor implements Filter {
@Override
public void destroy() {}
@Override
public void init(FilterConfig fc) throws ServletException {}
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
try {
EntityManagerHelper.beginTransaction();
chain.doFilter(req, res);
EntityManagerHelper.commit();
} catch (RuntimeException e) {
if ( EntityManagerHelper.getEntityManager() != null && EntityManagerHelper.getEntityManager().isOpen())
EntityManagerHelper.rollback();
throw e;
} finally {
EntityManagerHelper.closeEntityManager();
}
}
}
This approach also allows the View (e.g., a JSP page) to fetch entity's fields even if they have been lazy initialized (Open Session in View pattern).
In a JSE environment the EntityManagerFactory
needs to be explicitly closed when the servlet container is shutdown. This can be done by using a ServletContextListener
object:
public class EntityManagerFactoryListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent e) {
EntityManagerHelper.closeEntityManagerFactory();
}
@Override
public void contextInitialized(ServletContextEvent e) {}
}
The web.xml
deployment descriptor:
<listener>
<description>EntityManagerFactory Listener</description>
<listener-class>package.EntityManagerFactoryListener</listener-class>
</listener>
<filter>
<filter-name>interceptor</filter-name>
<filter-class>package.EntityManagerInterceptor</filter-class>
</filter>
<filter-mapping>
<filter-name>interceptor</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>