2013-02-25 21 views
10

Sto sviluppando un semplice progetto "Book Store" utilizzando Struts 1.3 + JPA (con Hibernate come provider di persistenza). Non posso passare a Spring o ad altri ambienti di sviluppo più sofisticati (ad es. Jboss) e non posso utilizzare alcuna tecnica specifica di Hibernate (ad esempio, classe Session).Filetto EntityManager Pattern locale con JPA in JSE

Dato che sono in un ambiente JSE, ho bisogno di gestire esplicitamente l'intero ciclo di vita di EntityManager.

L'entità Book è definito come segue:

@Entity 
public class Book { 

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

    // Getters and Setters 
} 

ho definito tre Action classi, che sono responsabili, rispettivamente, di recuperare tutte le istanze telefonico, recuperando una singola istanza libro dalla sua ISBN e fondendo un libro indipendente nel DB.

Al fine di aumentare la separazione delle preoccupazioni tra codice di business logic e codice di accesso ai dati, ho introdotto un semplice oggetto BookDAO, che è responsabile dell'esecuzione delle operazioni CRUD. Idealmente, tutte le chiamate relative all'accesso ai dati dovrebbero essere delegate al livello di persistenza. Ad esempio, il ListBookAction è definito come segue:

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

} 

L'oggetto BookDAO deve accedere un'istanza EntityManager per fare qualsiasi operazione. Dato che EntityManger non è thread-safe, ho introdotto una classe di supporto di nome BookUnitSession che incapsula EntityManager all'interno di una variabile 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; 
    } 

} 

Tutto sembra funzionare, ma ho ancora qualche preoccupazione. Vale a dire:

  1. Questa soluzione è la cosa migliore da fare? qual è la migliore pratica in questo caso?
  2. Ho ancora bisogno di chiudere esplicitamente sia EntityManager che EntityManagerFactory. Come lo posso fare?

Grazie

risposta

23

Negli ultimi giorni ho progettato una possibile soluzione. Quello che ho cercato di realizzare con la classe BookUnitSession era effettivamente la classe 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(); 
    } 
} 

Tale classe assicura che ciascun filo (cioè, ogni richiesta) avrà la propria EntityManager esempio. Di conseguenza, ciascun oggetto DAO può avere la corretta EntityManager esempio chiamando EntityManagerHelper.getEntityManager()

Secondo il modello sessione-per-richiesta ogni richiesta deve aprire e chiudere il proprio EntityManager esempio, che avrà il compito di incapsulare l'unità desiderata di lavoro all'interno di una transazione. Questo può essere fatto per mezzo di un filtro intercettare implementato come 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(); 
      } 
    } 
} 

questo approccio consente anche la visualizzazione (ad esempio, una pagina JSP) per andare a prendere i campi dell'entità anche se sono stato pigro inizializzato (Open Session Visualizza modello). In un ambiente JSE, EntityManagerFactory deve essere chiuso esplicitamente quando il contenitore del servlet viene arrestato.Questo può essere fatto utilizzando un oggetto ServletContextListener:

public class EntityManagerFactoryListener implements ServletContextListener { 

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

    @Override 
    public void contextInitialized(ServletContextEvent e) {} 

} 

Il descrittore di deployment 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> 
+0

Bel lavoro, ma probabilmente si intende JEE invece di JSE. –

+1

Grazie. Attualmente sto utilizzando Tomcat 7 e Struts 1.3 senza alcun server delle applicazioni. Pertanto, direi che non sono completamente conforme a JEE. Dal punto di vista della persistenza, è come essere in un ambiente JSE. Ecco perché ho dichiarato JSE nel titolo. –

+0

Ciao, questa soluzione ha funzionato alla fine? Ho sentito parlare di alcuni problemi con l'utilizzo di ThreadLocal. –

0

ScopedEntityManager strumento di aiuto che ho creato in Github usa una tecnica simile. Invece del filtro della richiesta, ho scelto ServletRequestListener per la gestione del ciclo di vita. Inoltre non sto usando un threadlocal perché hanno l'abitudine di perdite di memoria nei contenitori J2EE se non programmati attentamente. Tomcat ha alcuni trucchi per garantire alcuni errori umani.