Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
494 views
in Technique[技术] by (71.8m points)

hibernate - EntityManager ThreadLocal pattern with JPA in JSE

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:

  1. Is this solution the best thing to do? which is the best practice in this case?
  2. I still need to explictly close both the EntityManager and the EntityManagerFactory. How can I do that?

Thanks

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...