13

我正在使用 Struts 1.3 + JPA(使用 Hibernate 作为持久性提供者)开发一个简单的“书店”项目。我不能切换到 Spring 或任何其他更复杂的开发环境(例如,Jboss),也不能使用任何特定于 Hibernate 的技术(例如,Session类)。

鉴于我在 JSE 环境中,我需要明确管理整个 EntityManager 的生命周期。

Book实体定义如下:

@Entity
public class Book {

@Id private String isbn;
private String title;
private Date publishDate;

    // Getters and Setters
}

我定义了三个Action类,它们分别负责检索所有书籍实例、通过 ISBN 检索单个书籍实例以及将分离的书籍合并到数据库中。

为了增加业务逻辑代码和数据访问代码之间的关注点分离,我引入了一个简单的BookDAO对象,它负责执行 CRUD 操作。理想情况下,所有与数据访问相关的调用都应该委托给持久层。例如,ListBookAction定义如下:

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");
    }

}

BookDAO 对象需要访问一个EntityManager实例才能执行任何操作。鉴于这EntityManger不是线程安全的,我引入了一个名为的帮助类BookUnitSession,它将 EntityManager 封装在一个ThreadLocal变量中:

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;
    }

}

一切似乎都有效,但我仍然有些担心。即:

  1. 这个解决方案是最好的吗?在这种情况下,最佳做法是什么?
  2. 我仍然需要明确关闭 EntityManager 和 EntityManagerFactory。我怎样才能做到这一点?

谢谢

4

2 回答 2

25

在过去的几天里,我设计了一个可能的解决方案。我试图用这个BookUnitSession类构建的实际上是这个EntityManagerHelper类:

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();
    } 
}

这样的类确保每个线程(即每个请求)都将获得自己的EntityManager实例。因此,每个 DAO 对象都可以EntityManager通过调用EntityManagerHelper.getEntityManager()

根据每个请求的会话模式,每个请求都必须打开和关闭自己的EntityManager实例,该实例将负责将所需的工作单元封装在事务中。这可以通过实现为 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();
            }
    }
}

这种方法还允许视图(例如,JSP 页面)获取实体的字段,即使它们已经被延迟初始化(在视图中打开会话模式)。在 JSE 环境中,EntityManagerFactory需要在 servlet 容器关闭时显式关闭。这可以通过使用ServletContextListener对象来完成:

public class EntityManagerFactoryListener implements ServletContextListener {

    @Override
    public void contextDestroyed(ServletContextEvent e) {
        EntityManagerHelper.closeEntityManagerFactory();
    }

    @Override
    public void contextInitialized(ServletContextEvent e) {}

}

web.xml部署描述符:

<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>
于 2013-03-01T14:15:19.443 回答
0

我在 Github 中创建的ScopedEntityManager辅助工具使用了类似的技术。我没有选择请求过滤器,而是选择了 ServletRequestListener 进行生命周期管理。此外,我没有使用 threadlocal,因为如果不仔细编程,它们会在 J2EE 容器中出现内存泄漏的习惯。Tomcat 确实有一些技巧可以对某些人为错误进行故障保护。

于 2015-07-03T05:12:20.253 回答