我正在使用Struts 1.3 + JPA(使用Hibernate作为持久性提供程序)开发一个简单的“ Book Store”项目。我不能切换到Spring或任何其他更复杂的开发环境(例如Jboss),也不能使用任何特定于Hibernate的技术(例如,Session类)。
Session
考虑到我处于JSE环境中,我需要显式管理整个EntityManager的生命周期。
该Book实体被定义如下:
Book
@Entity public class Book { @Id private String isbn; private String title; private Date publishDate; // Getters and Setters }
我定义了三个Action类,分别负责检索所有书籍实例,通过其ISBN检索单个书籍实例并将独立的书籍合并到数据库中。
Action
为了增加业务逻辑代码和数据访问代码之间的关注点分离,我引入了一个简单的BookDAO对象,负责执行CRUD操作。理想情况下,所有与数据访问相关的调用都应委派给持久层。例如,ListBookAction定义如下:
BookDAO
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不是线程安全的,我引入了一个名为helper的类,该类BookUnitSession将EntityManager封装在一个ThreadLocal变量中:
EntityManager
EntityManger
BookUnitSession
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; } }
一切似乎正常,但是我仍然有一些担忧。即:
谢谢
在过去的几天中,我设计了一个可能的解决方案。我试图用BookUnitSession该类构造的实际上是EntityManagerHelper该类:
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()
EntityManagerHelper.getEntityManager()
根据每次请求会话模式,每个请求必须打开和关闭其自己的EntityManager实例,该实例负责将事务中所需的工作单元封装起来。这可以通过实现为的拦截过滤器来完成ServletFilter:
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(); } } }
这种方法还允许View(例如,JSP页面)获取实体的字段,即使它们已经被延迟初始化(View模式中的Open Session)。在JSE环境中,EntityManagerFactory当servlet容器关闭时,需要显式关闭。这可以通过使用一个ServletContextListener对象来完成:
EntityManagerFactory
ServletContextListener
public class EntityManagerFactoryListener implements ServletContextListener { @Override public void contextDestroyed(ServletContextEvent e) { EntityManagerHelper.closeEntityManagerFactory(); } @Override public void contextInitialized(ServletContextEvent e) {} }
在web.xml部署描述符:
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>