Ответ 1
В течение последних нескольких дней я разработал возможное решение. То, что я пытался построить с классом 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
, который будет отвечать за инкапсуляцию требуемой единицы работы внутри транзакции. Это можно сделать с помощью фильтра перехвата, реализованного как 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) выборку полей сущностей, даже если они были инициализированы ленивым способом (Open Session in View).
В среде JSE EntityManagerFactory
должно быть явно закрыто, когда контейнер сервлета завершен. Это можно сделать, используя объект 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>