熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

在WEB容器中獲取持久化上下文

2013-11-23 19:13:54  來源: Java核心技術 

  想寫這篇文章是因為看了網上相關的幾篇文章沒有一篇能真正說清楚如何正確地在WEB容器中獲取持久化上下文 要麼根本就不知道如何獲取要麼存在極度大的性能問題要麼存在線程安全性的隱患

  在EJB容器如果你願意由容器注入一個EntityManager後你可以把一切交給容器管理如果你願意使用可擴展事務 當然你已經了解如何管理EntityManager規范在那兒定著沒有什麼可多說的 在Java SE環境下沒有容器能幫助你做任何事一切都要由你純手工創建當然一切也由你負責管理關閉銷毀都是你的事所以反而變得簡單(是說獲取持久化手段簡單了不是操作單了)

  一容器管理持久化上下文

  而在WEB容器中一方面容器管理的持久化事務不能象EJB容器那樣可以使用擴展性事務要想讓容器管理那麼只能是 JTA事務而且數據源也一定是JTADATASOURCE常有人在網上找到一些persistencexml要麼使用了本地事務要麼是非JTA的數據源問我為什麼在WEB容器中不能成功注EntityManager

  要在WEB容器中讓容器管理持久化事務可以通過兩種方式一是容器注入一是JNDI查找這兩種方式並不是每個人都理解的我看到一本叫<<EJB JPA數據庫持久化編程詳解>>的書在網上受到太多的人的追捧(越是這樣的書受害者就越多!) 作者明確說容器托管的EntityManager只能運行在EJB容器中也就是說只有在EJBJAR包中才可以獲得容器托管的 EntityManager對象否則只能獲得應用托管的EntityManager對象

  事實上確實一些WEB容器不能注入EntityManager也不能通過JNDI查找到但是容器不支持並不是規范不支持如果你使用glassfish或者用resin 以上(目前已經)你就可以方便地獲得容器管理的EntityManager

    <?xml version= encoding=UTF?>

  <persistence xmlns=

  xmlns:xsi=instance

  xsi:schemaLocation= persistence__xsd

  version=>

  <persistenceunit name=jpaUnit transactiontype=JTA>

  <jtadatasource>jdbc/__axman</jtadatasource>

  <class>comaxmanCustomerEO</class>

  </persistenceunit>

  </persistence>

  <?xml version= encoding=UTF?>

  <persistence xmlns=

  xmlns:xsi=instance

  xsi:schemaLocation= persistence__xsd

  version=>

  <persistenceunit name=jpaUnit transactiontype=JTA>

  <jtadatasource>jdbc/__axman</jtadatasource>

  <class>comaxmanCustomerEO</class>

  </persistenceunit>

  </persistence>只要這個persistence xml文件在類路徑中就可以正確地注入EntityManager:

  //為了閱讀方便刪除了無關內容和注釋

  <PRE class=java name=code>public class TestAxman extends HttpServlet {

  @PersistenceContext(name=jpaUnit)

  private EntityManager em;

  @Resource

  private UserTransaction utx;

  protected void service(HttpServletRequest request HttpServletResponse response)

  throws ServletException IOException {

  PrintWriter out = responsegetWriter();

  CustomerEO ce = new CustomerEO();

  cesetName(p);

  cesetEmail();

  cesetAsset(d);

  cesetCreateDate(new javasqlDate(new javautilDate()getTime()));

  utxbegin();

  empersist(ce);

  mit();

  //容器管理事務不需要自己手工回滾只要告訴容器事務起止邊界

  }

  }

  </PRE>

  public class TestAxman extends HttpServlet {
                  @PersistenceContext(name=jpaUnit)
         private EntityManager em;
         @Resource
          private UserTransaction utx;
              protected void service(HttpServletRequest request HttpServletResponse response)
              throws ServletException IOException {
             PrintWriter out = responsegetWriter();
             CustomerEO ce = new CustomerEO();
             cesetName(p);
             cesetEmail();
             cesetAsset(d);
             cesetCreateDate(new javasqlDate(new javautilDate()getTime()));
             utxbegin();
             empersist(ce);
             mit();
             //容器管理事務不需要自己手工回滾只要告訴容器事務起止邊界
         }
         }
  public class TestAxman extends HttpServlet
{

  @PersistenceContext(name=jpaUnit)

  private EntityManager em;

  @Resource

  private UserTransaction utx;

  protected void service(HttpServletRequest request HttpServletResponse response)

  throws ServletException IOException {

  PrintWriter out = responsegetWriter();

  CustomerEO ce = new CustomerEO();

  cesetName(p);

  cesetEmail();

  cesetAsset(d);

  cesetCreateDate(new javasqlDate(new javautilDate()getTime()));

  utxbegin();

  empersist(ce);

  mit();

  //容器管理事務不需要自己手工回滾只要告訴容器事務起止邊界

  }

  }

  這樣注入進來的EntityManager完全由容器管理不要做任何EntityManager相關的工作但是一切就這樣完美了嗎? 當然不是和EJB容器不同的是相當於有狀態會話BEAN的Servlet是多線程服務的一個實例變量的EntityManager 完全可能會被多個線程同時訪問而出現極大的安全性隱患那麼這樣的注入是否有意義呢?  當然有意義一種情況是你可以在ServletContextListener這樣的線程安全模塊中注入另一種情況只要你能控制不讓多個線程同時訪問一個實例變量的EntityManager那麼你就可以享受容器管理帶來的方便性

  但是任何事物都有兩面性如果你要獲取方便就要犧牲應用范圍的控制和性能要讓多個線程不同時訪問一個實例變量EntityManager最終還是要進行同步或互斥即一個線程使用變量EntityManager時其它線程都要等待如果能在線程的local方法中(doXXX或父類的service方法中)獲取由容器管理的EntityManager 那將會大大提高程序的性能  容器管理的意思其實就是容器產生了一些對象你只需要拿來使用不需要負責它的產生和銷毀關鍵是容器產生了這樣的對象後你如何拿到通過注入只能注入成實例字段那麼在線程方法中可能通過JNDI 來即時獲取容器中的EntityManager(實事上只要改一下容器實現的源碼還可以通過在service方法中獲取ServletContext對象來動態即時注入但這對於普通程序員是做不到的)

  通過JNDI查找的jndiref也可以通過注釋或通過webxml配置兩種方法都可以: 注入jndiref:

  @PersistenceContext(name=persistence/jpaUnitunitName=jpaUnit)

  public class TestAxman extends HttpServlet {

  protected void service(HttpServletRequest requestHttpServletResponse response)

  throws ServletException IOException {

  Context env =(Context)newInitialContext()lookup(java:comp/env);

  EntityManager em = (EntityManager)envlookup(persistence/jpaUnit);

  Query query = emcreateQuery(SELECT c from CustomerEO c);

  List <CustomerEO> ls = querygetResultList();

  }

  }

  @PersistenceContext(name=persistence/jpaUnitunitName=jpaUnit)

  public class TestAxman extends HttpServlet {

  protected void service(HttpServletRequest requestHttpServletResponse response)

  throws ServletException IOException {

  Context env =(Context)newInitialContext()lookup(java:comp/env);

  EntityManager em = (EntityManager)envlookup(persistence/jpaUnit);

  Query query = emcreateQuery(SELECT c from CustomerEO c);

  List <CustomerEO> ls = querygetResultList();

  }

  }如果你不想用注釋可以在webxml中配置:

  <persistencecontextref>

  <persistencecontextrefname>persistence/jpaUnit</persistencecontextrefname>

  <persistenceunitname>jpaUnit</persistenceunitname>

  </persistencecontextref>

  <persistencecontextref>

  <persistencecontextrefname>persistence/jpaUnit</persistencecontextrefname>

  <persistenceunitname>jpaUnit</persistenceunitname>

  </persistencecontextref>然後可以同樣通過

  Context env =(Context)new InitialContext()lookup(java:comp/env);

  EntityManager em = (EntityManager)envlookup(persistence/jpaUnit); 查找到EntityManager

  需要說明的是JNDI查看本身是耗時的特別是new InitialContext()所以應該在一個全局的位置創建一個env (相當於工場)然後在service方法中通過這個env來查找EntityManager就可以大量地提升性能比如:

  public class TestAxman extends HttpServlet {

  Context env = null;

  protected void init(ServletConfig config) throws ServletException {

  env = (Context)newInitialContext()lookup(java:comp/env);

  }

  protected void service(HttpServletRequest request HttpServletResponse response)

  throws ServletException IOException {

  EntityManager em = (EntityManager)envlookup(persistence/jpaUnit);

  Query query = emcreateQuery(SELECT c from CustomerEO c);

  List <CustomerEO> ls = querygetResultList();

  }

  }

  public class TestAxman extends HttpServlet {

  Context env = null;

  protected void init(ServletConfig config) throws ServletException {

  env = (Context)newInitialContext()lookup(java:comp/env);

  }

  protected void service(HttpServletRequest request HttpServletResponse response)

  throws ServletException IOException {

  EntityManager em = (EntityManager)envlookup(persistence/jpaUnit);

  Query query = emcreateQuery(SELECT c from CustomerEO c);

  List <CustomerEO> ls = querygetResultList();

  }

  }

  <PRE class=java name=code>如果你需要容器管理持久化這種方案是最合適的方式雖然EntityManager 每次要在service中lookup出來但它是</PRE>
    <PRE class=java name=code>方法內的local變量不象注入成實例變量那樣存在多線程安全隱患</PRE>

  如果你需要容器管理持久化這種方案是最合適的方式雖然EntityManager 每次要在service中lookup出來但它是  如果你需要容器管理持久化這種方案是最合適的方式雖然EntityManager 每次要在service中lookup出來但它是
    方法內的local變量不象注入成實例變量那樣存在多線程安全隱患  方法內的local變量不象注入成實例變量那樣存在多線程安全隱患
    二應用管理持久化上下文:

  應用管理持久化上下文事實上就是EntityManager對象不是由容器負責產生和銷毀而是由應用程序來創建當然是   由應用程序來銷毀要由應用程序來創建持久化上下文就是要由EntityManagerFactory來進行createEntityManager   本著誰生產誰負責的原則當然要程序來負責銷毀所以應用管理的EntityManager一定要在finally語句中調用close()   方法這樣多少給我們使用它帶來不便但它也因為是應用程序創建所以有著廣泛的應用范圍無論是EJB容器還是WEB   容器或者是純Java SE環境都可以使用JPA功能

  要在WEB容器獲取EntityManagerFactory同樣可以通過注入和手工創建明白容器管理的意義應該知道注入  是容器已經產生了的對象所以EntityManagerFactory如果是容器注入的同樣不需要你手工銷毀而如果是手工  創建的則需要手工銷毀簡單說EntityManagerFactory對象本身也可以容器管理的:
    public class TestServlet extends HttpServlet {

  @PersistenceUnit(unitName = jpaUnit)

  private EntityManagerFactory emf;

  public void service(……) throws xxxExceotion{

  EntityManager em = emf createEntityManager();

  try{

  //invoke em;

  }

  finally{ emclose();} // EntityManager本身是應用程序創建的

  //所以必須手工關閉如果這裡是寫入操作事務還必須在cacth塊中手工回滾
        }

  }

  public class TestServlet extends HttpServlet {

  @PersistenceUnit(unitName = jpaUnit)

  private EntityManagerFactory emf;

  public void service(……) throws xxxExceotion{

  EntityManager em = emf createEntityManager();

  try{

  //invoke em;

  }

  finally{ emclose();} // EntityManager本身是應用程序創建的

  //所以必須手工關閉如果這裡是寫入操作事務還必須在cacth塊中手工回滾
     }

  }如果你不想通過容器注入EntityManagerFactory只要調用

  EntityManagerFactory emf = PersistencecreateEntityManagerFactory(jpaUnit);

  就可以獲取一個手工創建的EntityManagerFactory但是要記得在創建它的對應位置手要銷毀它如:

  public class TestServlet extends HttpServlet {

  private EntityManagerFactory emf;

  void init(){

  emf = PersistencecreateEntityManagerFactory(jpaUnit);

  }

  void destory(){

  if(emf != null) emfclose();

  }

  public void service() throws xxxExceotion{

  EntityManager em = emf createEntityManager();

  try{

  //invoke em;

  }

  finally{ emclose();} // EntityManager本身是應用程序創建的

  //所以必須手工關閉

  }

  }

  public class TestServlet extends HttpServlet {

  private EntityManagerFactory emf;

  void init(){

  emf = PersistencecreateEntityManagerFactory(jpaUnit);

  }

  void destory(){

  if(emf != null) emfclose();

  }

  public void service() throws xxxExceotion{

  EntityManager em = emf createEntityManager();

  try{

  //invoke em;

  }

  finally{ emclose();} // EntityManager本身是應用程序創建的

  //所以必須手工關閉

  }

  }因為EntityManagerFactory是工場對象所以上面的例子並不好最好的位置是在SerlvetContextListener中注入然後放在 ServletContext中或在ContextListener的contextInitialized中手工生成在contextDestroyed中銷毀生成後放入 ServletContext中供全局訪問即一個應用只有一個工場而EntityManager是在service方法中通過EntityManagerFactory 即時生成的這樣既可以提高性能又保證了線程安全性可以說是一個非常正確的方案

  同樣上面的那本書中在Servlet的init方法中生成一個實例對象EntityManagerFactory後在doPost方法中獲取 EntityManager本來是一個非常好的方案但作者卻說是不安全的非要用一個輔助的類來調用ThreadLocal來在doPost方法中獲取EntityManager難道不同線程的doPost方法內的local變量也能被其它線程訪問?純是蛇足之舉


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26497.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.