2013-02-25 11 views
10

Zajmuję się tworzeniem prostego projektu "Book Store" przy użyciu Struts 1.3 + JPA (z Hibernate jako dostawcą trwałości). Nie mogę przełączyć się na Spring lub inne bardziej zaawansowane środowisko programistyczne (np. Jboss) i nie mogę używać żadnej konkretnej techniki Hibernate (np. Klasa Session).EntityManager ThreadLocal pattern with JPA w JSE

Biorąc pod uwagę, że jestem w środowisku JSE, muszę jawnie zarządzać całym cyklem życia EntityManager.

Jednostka Book jest zdefiniowany w następujący sposób:

@Entity 
public class Book { 

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

    // Getters and Setters 
} 

I określonych trzy Action klas, które odpowiadają, odpowiednio, pobierania wszystkich przypadków książki, odzyskanie jednego wystąpienia książki po ISBN i łączenia odłączoną książki do DB.

W celu zwiększenia rozbieżności między kodami biznes-logicznymi a kodem dostępu do danych, wprowadziłem prosty obiekt BookDAO, który jest odpowiedzialny za wykonywanie operacji CRUD. W idealnym przypadku wszystkie połączenia związane z dostępem do danych powinny być delegowane do warstwy trwałości. Na przykład, ListBookAction jest zdefiniowany w następujący sposób:

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

} 

Przedmiotem BookDAO potrzebuje uzyskać dostęp do wystąpienia EntityManager w tym celu każdą operację. Biorąc pod uwagę, że EntityManger nie jest bezpieczny wątku, I wprowadził klasę pomocniczą o nazwie BookUnitSession który hermetyzuje EntityManager w zmiennej 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; 
    } 

} 

Wszystko wydaje się działać, ale nadal mam pewne obawy. Mianowicie:

  1. Czy to rozwiązanie jest najlepsze? która jest najlepsza praktyka w tym przypadku?
  2. Nadal muszę wyraźnie zamknąć zarówno EntityManager, jak i EntityManagerFactory. Jak mogę to zrobić?

Dzięki

Odpowiedz

23

w ciągu ostatnich kilku dni zaprojektowany możliwe rozwiązanie. Co starałem się zbudować z klasą BookUnitSession był rzeczywiście klasa 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(); 
    } 
} 

Taka klasa gwarantuje, że każdy wątek (czyli każde żądanie) dostanie własny EntityManager instancji. W konsekwencji, każdy obiekt DAO można uzyskać prawidłową EntityManager instancji poprzez wywołanie EntityManagerHelper.getEntityManager()

według wzoru session-per-życzenie każdy wniosek musi otwierać i zamykać własną EntityManager instancję, która będzie odpowiedzialna za enkapsulacji wymaganą jednostkę pracy w ramach transakcji. Można to zrobić za pomocą przechwytujący filtrze zaimplementowany jako 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(); 
      } 
    } 
} 

Takie podejście pozwala również pogląd (np stronę JSP) do pobierania pola jednostki, nawet jeśli zostały one leniwy zainicjowany (Open Session w Zobacz wzór). W środowisku JSE wartość EntityManagerFactory musi być jawnie zamknięta po zamknięciu kontenera serwletu.Można to zrobić za pomocą ServletContextListener obiektu:

public class EntityManagerFactoryListener implements ServletContextListener { 

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

    @Override 
    public void contextInitialized(ServletContextEvent e) {} 

} 

deskryptorze web.xml Wdrożenie:

<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> 
+0

Dobra robota, ale prawdopodobnie masz na myśli JEE zamiast JSE. –

+1

Dziękuję. Obecnie używam Tomcat 7 i Struts 1.3 bez żadnego serwera aplikacji. Dlatego powiedziałbym, że nie jestem całkowicie zgodny z JEE. Z punktu widzenia trwałości, jest jak bycie w środowisku JSE. Właśnie dlatego podałem tytuł JSE w tytule. –

+0

Witam, czy to rozwiązanie wyszło na końcu? Słyszałem o pewnych problemach z użyciem ThreadLocal. –

0

ScopedEntityManager pomocnik narzędzie Stworzyłem w Github wykorzystuje podobną technikę. Zamiast filtra żądania wybrałem ServletRequestListener do zarządzania cyklem życia. Również nie używam wątku, ponieważ mają nawyk wycieków pamięci w kontenerach J2EE, jeśli nie zostały starannie zaprogramowane. Tomcat ma pewne sztuczki, które mogą uszkodzić pewne ludzkie błędy.

Powiązane problemy