13

Struts 1.3 + JPA (永続化プロバイダーとして Hibernate を使用) を使用して、単純な「Book Store」プロジェクトを開発しています。Spring やその他のより洗練された開発環境 (Jboss など) に切り替えることができず、Hibernate 固有の手法 (Sessionクラスなど) を使用することもできません。

私が JSE 環境にいるという事実を考えると、EntityManager のライフサイクル全体を明示的に管理する必要があります。

エンティティは次のBookように定義されます。

@Entity
public class Book {

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

    // Getters and Setters
}

私は 3 つのActionクラスを定義しました。これらのクラスはそれぞれ、すべての書籍インスタンスの取得、ISBN による単一の書籍インスタンスの取得、および分離された書籍の DB へのマージを担当します。

ビジネス ロジック コードとデータ アクセス コードの間の関心の分離を強化するために、BookDAOCRUD 操作の実行を担当する単純なオブジェクトを導入しました。理想的には、すべてのデータ アクセス関連の呼び出しを永続化レイヤーに委任する必要があります。たとえば、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これはスレッドセーフではないため、変数内に EntityManager をカプセル化するという名前のヘルパー クラスを導入しましたBookUnitSessionThreadLocal

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インスタンスを開いたり閉じたりする必要があります。これは、トランザクション内で必要な作業単位をカプセル化する役割を果たします。これは、次のように実装されたインターセプト フィルターによって実行できます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、サーブレット コンテナーをシャットダウンするときに明示的に閉じる必要があります。これは、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 を選択しました。また、慎重にプログラムしないと、J2EE コンテナでメモリ リークが発生する傾向があるため、threadlocal は使用していません。Tomcat には、特定の人的エラーをフェールセーフするためのいくつかのトリックがあります。

于 2015-07-03T05:12:20.253 に答える