EntityManager详解

持久化上下文(Persistence Contexts)的相关知识,内容包括如何从Java EE容器中创建EntityManager对象、如何从Java SE中创建EntityManager对象、持久化上下文与事务(Transction)的关系,以及实体管理器工厂(Entity Manager Factory)的相关内容。 
通过本章的学习,读者将深入掌握JPA中有关持久化上下文、事务处理的相关知识,从而能够更加深入地应用JPA。 


11.1 获得EntityManager对象 

那么如何获得EntityManager对象呢?这又是JPA中另外一个很重要的问题。 


11.1.1  Java EE环境与J2SE环境 

在详细讲述EntityManager对象之前,读者首先要分清楚两个概念,即Java EE环境与J2SE环境。因为在本章后面的学习中要经常提到这两个概念,所以读者一定要先理解它们,为以后的学习打好基础。 


— Java EE环境,包括EJB容器和Web容器。 

(1)Web容器:只运行Web应用的容器,例如Tomcat就是开源的Web容器,它可以运行JSP、Servlet等。 

(2)EJB容器:运行在EJB组件的容器,提供EJB组件的状态管理、事务管理、线程管理、远程数据资源访问、连接管理和安全性管理等系统级服务。例如JBoss为EJB容器和Web容器(Web容器是集成了Tomcat)结合。 

部署在EJB容器中的JAR包都可以认为是运行在EJB容器中。但JBoss中的Web应用,比如war包中的类就不是运行在EJB容器中,而是运行在Web容器中。 

— J2SE环境 

最普通Java运行环境,例如一个HelloWorld的Java程序就是运行在J2SE的环境中,通常使用main入口方法作为程序启动的触发。 

如图11-1所示,它说明了Java EE与J2SE环境的关系。 

11.1.2  两种类型的EntityManager对象 

根据EntityManager对象的管理方式,可以有以下两种类型。 

— 容器托管的(container-managed)EntityManager对象 

容器托管的EntityManager对象最简单,程序员不需要考虑EntityManager连接的释放,以及事务等复杂的问题,所有这些都交给容器去管理。容器托管的EntityManager对象必须在EJB容器中运行,而不能在Web容器和J2SE的环境中运行。本书前面讲述的EntityManager对象都是通过注入 @PersistenceContext注释来获得的,其实,这种获得EntityManager对象的方式就是容器托管的。 

— 应用托管的(application-managed)EntityManager对象 

应用托管的EntityManager对象,程序员需要手动地控制它的释放和连接、手动地控制事务等。但这种获得应用托管的EntityManager对象的方式,不仅可以在EJB容器中应用,也可以使 JPA脱离EJB容器,而与任何的Java环境集成,比如说Web容器、J2SE环境等。所以从某种角度上来说,这种方式是JPA能够独立于EJB环境运行的基础。 

理想状态下,最好是选用容器托管的EntityManager对象的方式,但在特殊的环境下,还是需要使用应用托管的EntityManager对象这种方式。 

正是因为应用托管的EntityManager对象的连接释放、事务控制比较复杂,所以在使用时涉及的相关内容比较多,这些内容将在本章后面部分详细讲述,这里读者应对两种方式有一个大致的了解,两种EntityManager对象类型的比较如表11-1所示。 

表11-1  容器托管与应用托管的EntityManager对象对比 

比较内容 

容器托管的(container-managed)EntityManager对象 

应用托管的(application-managed)EntityManager对象 

获得方式 

两种方式:1 @PersistenceContex注入 2 JNDI获得 

EntityManagerFactory创建 

支持事务 

JTA 

JTA、RESOURCE_LOCAL 

运行环境 

EJB容器 

EJB容器、Web容器、J2SE环境 

11.1.3  容器托管的(container-managed)EntityManager对象 

容器托管的EntityManager对象只能运行在EJB容器中。所以可以这样理解,只有在EJB-JAR包中,才可以获得容器托管的EntityManager对象,否则只能获得应用托管的EntityManager对象。 

在EJB容器中获得EntityManager对象主要有两种方式,即@PersistenceContext注释注入和JNDI方式获得。 

11.1.3.1  通过@PersistenceContext注释注入 

这种方式获得EntityManager对象最为常用,例如下面代码所示。 

Java代码   收藏代码
  1. @Stateless  
  2.   
  3. public class CustomerService implements ICustomerService {  
  4.   
  5.     @PersistenceContext(unitName = "jpaUnit")  
  6.   
  7.     private EntityManager entityManager;  
  8.   
  9.     public List findAllCustomers() {  
  10.   
  11.         Query query = entityManager.createQuery("SELECT c FROM CustomerEO c");  
  12.   
  13.         List result = query.getResultList();  
  14.   
  15.         for (CustomerEO c : result) {  
  16.   
  17.             System.out.println(c.getId()+","+c.getName());  
  18.   
  19.         }  
  20.   
  21.         return result;  
  22.   
  23.     }  
  24.   
  25. }  


在使用此种方式创建EntityManager对象时,需要注意以下几个问题。 

— @PersistenceContext注释中,其中unitName为persistence.xml文件中元素中的属性“name”的值,表示要初始化哪个持久化单元,如下所示。 

Xml代码   收藏代码
  1. <persistence>  
  2.   
  3.     <persistence-unit name="jpaUnit" transaction-type="JTA">  
  4.   
  5.     persistence-unit>  
  6.   
  7. persistence>  


— @PersistenceContext注释中还可以配置其他的设置,它的定义如下所示。 

Java代码   收藏代码
  1. @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME)  
  2.   
  3. public @interface PersistenceContext{  
  4.   
  5.     String name() default "";  
  6.   
  7.     String unitName() default "";  
  8.   
  9.     PersistenceContextType type default TRANSACTION;  
  10.   
  11.     PersistenceProperty[] properties() default {};  
  12.   
  13. }  


— 其中PersistenceContextType可以设置创建EntityManager对象时,持久化上下文的作用范围,它的意义在于对有状态的Bean(Stateless Bean)可以跨事务操作实体。它主要有两种方式,定义如下所示。 
Java代码   收藏代码
  1. public enum PersistenceContextType {  
  2.   
  3.     TRANSACTION,  
  4.   
  5.     EXTENDED  
  6.   
  7. }  


默认情况下使用TRANSACTION,有关TRANSACTION方式和EXTENDED方式创建EntityManager对象的异同,将在下文中详细讲述,这里读者简单了解一下即可。 

11.1.3.2  通过JNDI的方式获得 

如果指定了@PersistenceContext注释中的name值,则设置了持久化上下文的JNDI名称。通过SessionContext可以创建EntityManager对象。 

例如,下面代码为通过JNDI方式获得EntityManager对象。 

Java代码   收藏代码
  1. @Stateless  
  2.   
  3. @PersistenceContext(name="jpa")  
  4.   
  5. public class CustomerService implements ICustomerService {  
  6.   
  7.     @Resource  
  8.   
  9.     SessionContext ctx;  
  10.   
  11.     public List findAllCustomers() {  
  12.   
  13.         EntityManager entityManager = (EntityManager) ctx.lookup("jpa");  
  14.   
  15.         Query query = entityManager.createQuery("SELECT c FROM CustomerEO c");  
  16.   
  17.         List result = query.getResultList();  
  18.   
  19.         for (CustomerEO c : result) {  
  20.   
  21.             System.out.println(c.getId()+","+c.getName());  
  22.   
  23.         }  
  24.   
  25.         return result;  
  26.   
  27.     }  
  28.   
  29. }  


11.1.4  应用托管的(application-managed)EntityManager对象 

应用托管的EntityManager对象,不仅可以在Java EE环境中获得,也可以应用在J2SE的环境中。但无论是在什么情况下获得的EntityManager对象,都是通过实体管理器工厂(EntityManagerFactory)对象创建的。所以如何获得应用托管的EntityManager对象关键是 EntityManagerFactory对象如何获得。 

下面就分别讲述在EJB容器、Web容器和J2SE环境中如何获得EntityManagerFactory对象。 

11.1.4.1  EJB容器中获得 

在EJB容器中,EntityManagerFactory对象可以通过使用注入@PersistenceUnit注释获得,例如下面代码为在EJB容器中,获得应用托管的EntityManager对象的方法。 

Java代码   收藏代码
  1. @Stateless  
  2.   
  3. public class CustomerService implements ICustomerService {  
  4.   
  5.     @PersistenceUnit(unitName="jpaUnit")  
  6.   
  7.     private EntityManagerFactory emf;  
  8.   
  9.     public List findAllCustomers() {  
  10.   
  11.         /**创建EntityManager对象*/  
  12.   
  13.         EntityManager em = emf.createEntityManager();  
  14.   
  15.         Query query = em.createQuery("SELECT c FROM CustomerEO c");  
  16.   
  17.         List result = query.getResultList();  
  18.   
  19.         for (CustomerEO c : result) {  
  20.   
  21.             System.out.println(c.getId()+","+c.getName());  
  22.   
  23.         }  
  24.   
  25.         /**关闭EntityManager */  
  26.   
  27.         em.close();  
  28.   
  29.         return result;  
  30.   
  31.     }  
  32.   
  33. }  

通过以上的EntityManager对象代码,可以总结出以下几个问题。 

— 应用托管的EntityManager对象,要在代码中手动地创建和关闭,例如下面代码所示。 

EntityManager em = emf.createEntityManager(); 

/**其他的业务逻辑*/ 

em.close(); 

这点正是与容器托管的EntityManager对象的最大不同之处。事实上,容器托管的EntityManager对象,它的创建和关闭是由容器负责管理的,所以不需要编写代码来控制。 

— 应用托管的EntityManager对象,都是通EntityManagerFactory对象来创建的。在容器中可以通过使用注入@PersistenceUnit注释的方法实现,它的定义如下所示。 

Java代码   收藏代码
  1. @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME)  
  2.   
  3. public @interface PersistenceUnit{  
  4.   
  5.     String name() default "";  
  6.   
  7.     String unitName() default "";  
  8.   
  9. }  


其中,属性unitName为persistence.xml文件中元素中的属性“name”的值,表示要初始化哪个持久化单元,与@PersistenceContext注释中unitName属性相同。 

11.1.4.2  Web容器中获得 

在Web容器中,EntityManagerFactory对象也可以通过使用注入@PersistenceUnit注释获得。例如,下面代码为在Servlet中,获得应用托管的EntityManager对象的方法。 

Java代码   收藏代码
  1. public class TestServlet extends HttpServlet {  
  2.   
  3.     @PersistenceUnit(unitName = "jpaUnit")  
  4.   
  5.     private EntityManagerFactory emf;  
  6.   
  7.     public TestServlet() {  
  8.   
  9.         super();  
  10.   
  11.     }  
  12.   
  13.     public void doGet(HttpServletRequest request, HttpServletResponse response)  
  14.   
  15.             throws ServletException, IOException {  
  16.   
  17.         doPost(request, response);  
  18.   
  19.     }  
  20.   
  21.     public void doPost(HttpServletRequest request, HttpServletResponse response)  
  22.   
  23.             throws ServletException, IOException {  
  24.   
  25.         response.setContentType("text/html");  
  26.   
  27.         PrintWriter out = response.getWriter();  
  28.   
  29.         out.println("");  
  30.   
  31.         out.println("");  
  32.   
  33.         out.println("  A Servlet");  
  34.   
  35.         out.println("  ");  
  36.   
  37.         if (emf != null) {  
  38.   
  39.             /**创建EntityManager 对象*/  
  40.   
  41.             EntityManager entityManager = emf.createEntityManager();  
  42.   
  43.             try {  
  44.   
  45.                 Query query = entityManager  
  46.   
  47.                         .createQuery("SELECT c FROM CustomerEO c");  
  48.   
  49.                 List result = query.getResultList();  
  50.   
  51.                 for (CustomerEO c : result) {  
  52.   
  53.                     System.out.println(c.getId() + "," + c.getName());  
  54.   
  55.                 }  
  56.   
  57.             } finally {  
  58.   
  59.                 /**关闭EntityManager*/  
  60.   
  61.                 entityManager.close();  
  62.   
  63.             }  
  64.   
  65.         }  
  66.   
  67.         out.println("  ");  
  68.   
  69.         out.println("");  
  70.   
  71.         out.flush();  
  72.   
  73.         out.close();  
  74.   
  75.     }  
  76.   
  77. }  


由于是通过注入方法获得的EntityManagerFactory对象,所以EntityManagerFactory对象的创建不需要手动创建和关闭,这里与在EJB容器中的获得方法相同。 

★ 提示 ★ 

目前JBoss 4.2集成了的Tomcat版本为5.5,但Tomcat 6.0以后的版本中才支持使用注释,所以如果将本例中Servlet运行在JBoss 4.2中,并不能获得EntityManagerFactory对象;但在符合J2EE 5.0的服务器中,这样运行是可以的。 

虽然在目前JBoss 4.2版本中不支持使用注释,但可以通过另一种方式来获得应用托管的EntityManager对象。代码如下所示。 

Java代码   收藏代码
  1. public class TestServlet extends HttpServlet {  
  2.   
  3.     private EntityManagerFactory emf;  
  4.   
  5.     public TestServlet() {  
  6.   
  7.         super();  
  8.   
  9.     }  
  10.   
  11.     public void doGet(HttpServletRequest request, HttpServletResponse response)  
  12.   
  13.             throws ServletException, IOException {  
  14.   
  15.         doPost(request, response);  
  16.   
  17.     }  
  18.   
  19.     public void doPost(HttpServletRequest request, HttpServletResponse response)  
  20.   
  21.             throws ServletException, IOException {  
  22.   
  23.         response.setContentType("text/html");  
  24.   
  25.         PrintWriter out = response.getWriter();  
  26.   
  27.         out.println("");  
  28.   
  29.         out.println("");  
  30.   
  31.         out.println("  A Servlet");  
  32.   
  33.         out.println("  ");  
  34.   
  35.         if (emf != null) {  
  36.   
  37.             /**创建EntityManager 对象*/  
  38.   
  39.             EntityManager entityManager = emf.createEntityManager();  
  40.   
  41.             try {  
  42.   
  43.                 Query query = entityManager  
  44.   
  45.                         .createQuery("SELECT c FROM CustomerEO c");  
  46.   
  47.                 List result = query.getResultList();  
  48.   
  49.                 for (CustomerEO c : result) {  
  50.   
  51.                     System.out.println(c.getId() + "," + c.getName());  
  52.   
  53.                 }  
  54.   
  55.             } finally {  
  56.   
  57.                 /**关闭EntityManager*/  
  58.   
  59.                 entityManager.close();  
  60.   
  61.             }  
  62.   
  63.         }  
  64.   
  65.         out.println("  ");  
  66.   
  67.         out.println("");  
  68.   
  69.         out.flush();  
  70.   
  71.         out.close();  
  72.   
  73.     }  
  74.   
  75.     /**Servlet初始化时,创建EntityManagerFactory 对象*/  
  76.   
  77.     public void init() throws ServletException {  
  78.   
  79.         if (emf == null) {  
  80.   
  81.             emf = Persistence.createEntityManagerFactory("jpaUnit");  
  82.   
  83.         }  
  84.   
  85.     }  
  86.   
  87.     /**Servlet销毁时,关闭EntityManagerFactory对象*/  
  88.   
  89.     public void destroy() {  
  90.   
  91.         if (emf.isOpen())  
  92.   
  93.             emf.close();  
  94.   
  95.     }  
  96.   
  97. }  


使用这种方式创建EntityManagerFactory对象需要注意以下几个问题。 

— 可以看到,这里的EntityManagerFactory对象不是通过注入获得的,而是通过Persistence类中的静态方法createEntityManagerFactory来创建的。 

— 正因为EntityManagerFactory对象是手动创建的,所以在不再使用时,一定要调用close()方法手动关闭。 

11.1.4.3  J2SE环境中获得 

在J2SE环境中,获得应用托管的EntityManager对象只能通过手动创建的方式,而不能使用注释的方式,与Web容器中不使用注释的方法相同,都是通过Persistence类中createEntityManagerFactory来创建的。 

例如,下面代码为J2SE环境中获得应用托管EntityManager对象的方法。 

Java代码   收藏代码
  1. public class CustomerClient {  
  2.   
  3.     public static void main(String[] args) {  
  4.   
  5.         /** 创建EntityManagerFactory对象 */  
  6.   
  7.         EntityManagerFactory emf = Persistence  
  8.   
  9.                 .createEntityManagerFactory("jpaUnit");  
  10.   
  11.         /** 创建entityManager对象 */  
  12.   
  13.         EntityManager entityManager = emf.createEntityManager();  
  14.   
  15.         Query query = entityManager.createQuery("SELECT c FROM CustomerEO c");  
  16.   
  17.         List result = query.getResultList();  
  18.   
  19.         for (CustomerEO c : result) {  
  20.   
  21.             System.out.println(c.getId() + "," + c.getName());  
  22.   
  23.         }  
  24.   
  25.         /** 关闭entityManager对象 */  
  26.   
  27.         entityManager.close();  
  28.   
  29.         /** 关闭EntityManagerFactory对象 */  
  30.   
  31.         emf.close();  
  32.   
  33.     }  
  34.   
  35. }  

但是,在J2SE环境中使用JPA需要将实现的JPA的第三方类包和数据库的驱动包,设置到当前的运行环境下。 

例如,在Eclipse中创建一个Java项目,需要将JPA实现者的类库(这里以Hibernate为例)和MySQL的数据库连接包添加到构建路径中,如图11-2所示。 

11.1.5  ThreadLocal的使用 

对于在Web容器中使用EntityManager对象,这里需要做一些改进,才能更安全。读者应该了解,Servlet是非线程安全的,所以需要改变获得EntityManager对象的方式,这里笔者建议使用ThreadLocal类。 

ThreadLocal就是为每一个使用某变量的线程都提供一个该变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有一个该变量,这就解决了Servlet非线程安全的问题。 

首先编写一个EntityManagerHelper类,代码如下所示。 

Java代码   收藏代码
  1. public class EntityManagerHelper {  
  2.   
  3.      
  4.   
  5.     private static final EntityManagerFactory emf;  
  6.   
  7.     private static final ThreadLocal threadLocal;  
  8.   
  9.      
  10.   
  11.     /**初始化*/  
  12.   
  13.     static {  
  14.   
  15.         emf = Persistence.createEntityManagerFactory("jpaUnit");         
  16.   
  17.         threadLocal = new ThreadLocal();  
  18.   
  19.     }  
  20.   
  21.      
  22.   
  23.     /**通过threadLocal 获得EntityManager 对象*/  
  24.   
  25.     public static EntityManager getEntityManager() {  
  26.   
  27.         EntityManager manager = threadLocal.get();       
  28.   
  29.         if (manager == null || !manager.isOpen()) {  
  30.   
  31.             manager = emf.createEntityManager();  
  32.   
  33.             threadLocal.set(manager);  
  34.   
  35.         }  
  36.   
  37.         return manager;  
  38.   
  39.     }  
  40.   
  41.      
  42.   
  43.     /**关闭EntityManager 对象*/  
  44.   
  45.     public static void closeEntityManager() {  
  46.   
  47.         EntityManager em = threadLocal.get();  
  48.   
  49.         threadLocal.set(null);  
  50.   
  51.         if (em != null) em.close();  
  52.   
  53.     }  
  54.   
  55.      
  56.   
  57.     public static void beginTransaction() {  
  58.   
  59.     getEntityManager().getTransaction().begin();  
  60.   
  61.     }  
  62.   
  63.      
  64.   
  65.     public static void commit() {  
  66.   
  67.     getEntityManager().getTransaction().commit();  
  68.   
  69.     }   
  70.   
  71.      
  72.   
  73.     public static Query createQuery(String query) {  
  74.   
  75.         return getEntityManager().createQuery(query);  
  76.   
  77.     }  
  78.   
  79.      
  80.   
  81. }  


这样经过改进后,在Servlet中创建EntityManager对象的方法修改为如下所示。 

Java代码   收藏代码
  1. public class TestServlet extends HttpServlet {  
  2.   
  3.     public TestServlet() {  
  4.   
  5.         super();  
  6.   
  7.     }  
  8.   
  9.     public void doPost(HttpServletRequest request, HttpServletResponse response)  
  10.   
  11.             throws ServletException, IOException {  
  12.   
  13.             EntityManager entityManager = EntityManagerHelper.getEntityManager();  
  14.   
  15.             try {  
  16.   
  17.                 Query query = entityManager  
  18.   
  19.                         .createQuery("SELECT c FROM CustomerEO c");  
  20.   
  21.                 List result = query.getResultList();  
  22.   
  23.                 for (CustomerEO c : result) {  
  24.   
  25.                     System.out.println(c.getId() + "," + c.getName());  
  26.   
  27.                 }  
  28.   
  29.             } finally {  
  30.   
  31.                 /**关闭EntityManager*/  
  32.   
  33.                 EntityManagerHelper.closeEntityManager();  
  34.   
  35.             }  
  36.   
  37.     }  
  38.   

你可能感兴趣的:(JNDI,JPA,EntityManager,实)