持久化上下文(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對象最為常用,例如下面代碼所示。
- @Stateless
-
- public class CustomerService implements ICustomerService {
-
- @PersistenceContext(unitName = "jpaUnit")
-
- private EntityManager entityManager;
-
- public List<CustomerEO> findAllCustomers() {
-
- Query query = entityManager.createQuery("SELECT c FROM CustomerEO c");
-
- List<CustomerEO> result = query.getResultList();
-
- for (CustomerEO c : result) {
-
- System.out.println(c.getId()+","+c.getName());
-
- }
-
- return result;
-
- }
-
- }
@Stateless public class CustomerService implements ICustomerService { @PersistenceContext(unitName = "jpaUnit") private EntityManager entityManager; public List<CustomerEO> findAllCustomers() { Query query = entityManager.createQuery("SELECT c FROM CustomerEO c"); List<CustomerEO> result = query.getResultList(); for (CustomerEO c : result) { System.out.println(c.getId()+","+c.getName()); } return result; } }
在使用此種方式創建EntityManager對象時,需要注意以下幾個問題。
— @PersistenceContext注釋中,其中unitName為persistence.xml文件中<persistence-unit>元素中的屬性“name”的值,表示要初始化哪個持久化單元,如下所示。
- <persistence>
-
- <persistence-unit name="jpaUnit" transaction-type="JTA">
-
- </persistence-unit>
-
- </persistence>
<persistence> <persistence-unit name="jpaUnit" transaction-type="JTA"> </persistence-unit> </persistence>
— @PersistenceContext注釋中還可以配置其他的設置,它的定義如下所示。
- @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME)
-
- public @interface PersistenceContext{
-
- String name() default "";
-
- String unitName() default "";
-
- PersistenceContextType type default TRANSACTION;
-
- PersistenceProperty[] properties() default {};
-
- }
@Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface PersistenceContext{ String name() default ""; String unitName() default ""; PersistenceContextType type default TRANSACTION; PersistenceProperty[] properties() default {}; }
— 其中PersistenceContextType可以設置創建EntityManager對象時,持久化上下文的作用范圍,它的意義在于對有狀態的Bean(Stateless Bean)可以跨事務操作實體。它主要有兩種方式,定義如下所示。
- public enum PersistenceContextType {
-
- TRANSACTION,
-
- EXTENDED
-
- }
public enum PersistenceContextType { TRANSACTION, EXTENDED }
默認情況下使用TRANSACTION,有關TRANSACTION方式和EXTENDED方式創建EntityManager對象的異同,將在下文中詳細講述,這里讀者簡單了解一下即可。
11.1.3.2 通過JNDI的方式獲得
如果指定了@PersistenceContext注釋中的name值,則設置了持久化上下文的JNDI名稱。通過SessionContext可以創建EntityManager對象。
例如,下面代碼為通過JNDI方式獲得EntityManager對象。
- @Stateless
-
- @PersistenceContext(name="jpa")
-
- public class CustomerService implements ICustomerService {
-
- @Resource
-
- SessionContext ctx;
-
- public List<CustomerEO> findAllCustomers() {
-
- EntityManager entityManager = (EntityManager) ctx.lookup("jpa");
-
- Query query = entityManager.createQuery("SELECT c FROM CustomerEO c");
-
- List<CustomerEO> result = query.getResultList();
-
- for (CustomerEO c : result) {
-
- System.out.println(c.getId()+","+c.getName());
-
- }
-
- return result;
-
- }
-
- }
@Stateless @PersistenceContext(name="jpa") public class CustomerService implements ICustomerService { @Resource SessionContext ctx; public List<CustomerEO> findAllCustomers() { EntityManager entityManager = (EntityManager) ctx.lookup("jpa"); Query query = entityManager.createQuery("SELECT c FROM CustomerEO c"); List<CustomerEO> result = query.getResultList(); for (CustomerEO c : result) { System.out.println(c.getId()+","+c.getName()); } return result; } }
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對象的方法。
- @Stateless
-
- public class CustomerService implements ICustomerService {
-
- @PersistenceUnit(unitName="jpaUnit")
-
- private EntityManagerFactory emf;
-
- public List<CustomerEO> findAllCustomers() {
-
-
-
- EntityManager em = emf.createEntityManager();
-
- Query query = em.createQuery("SELECT c FROM CustomerEO c");
-
- List<CustomerEO> result = query.getResultList();
-
- for (CustomerEO c : result) {
-
- System.out.println(c.getId()+","+c.getName());
-
- }
-
-
-
- em.close();
-
- return result;
-
- }
-
- }
@Stateless public class CustomerService implements ICustomerService { @PersistenceUnit(unitName="jpaUnit") private EntityManagerFactory emf; public List<CustomerEO> findAllCustomers() { /**創建EntityManager對象*/ EntityManager em = emf.createEntityManager(); Query query = em.createQuery("SELECT c FROM CustomerEO c"); List<CustomerEO> result = query.getResultList(); for (CustomerEO c : result) { System.out.println(c.getId()+","+c.getName()); } /**關閉EntityManager */ em.close(); return result; } }
通過以上的EntityManager對象代碼,可以總結出以下幾個問題。
— 應用托管的EntityManager對象,要在代碼中手動地創建和關閉,例如下面代碼所示。
EntityManager em = emf.createEntityManager();
/**其他的業務邏輯*/
em.close();
這點正是與容器托管的EntityManager對象的最大不同之處。事實上,容器托管的EntityManager對象,它的創建和關閉是由容器負責管理的,所以不需要編寫代碼來控制。
— 應用托管的EntityManager對象,都是通EntityManagerFactory對象來創建的。在容器中可以通過使用注入@PersistenceUnit注釋的方法實現,它的定義如下所示。
- @Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME)
-
- public @interface PersistenceUnit{
-
- String name() default "";
-
- String unitName() default "";
-
- }
其中,屬性unitName為persistence.xml文件中<persistence-unit>元素中的屬性“name”的值,表示要初始化哪個持久化單元,與@PersistenceContext注釋中unitName屬性相同。
11.1.4.2 Web容器中獲得
在Web容器中,EntityManagerFactory對象也可以通過使用注入@PersistenceUnit注釋獲得。例如,下面代碼為在 Servlet中,獲得應用托管的EntityManager對象的方法。 /syntaxhighlighter/clipboard_new.swf">
- public class TestServlet extends HttpServlet {
-
- @PersistenceUnit(unitName = "jpaUnit")
-
- private EntityManagerFactory emf;
-
- public TestServlet() {
-
- super();
-
- }
-
- public void doGet(HttpServletRequest request, HttpServletResponse response)
-
- throws ServletException, IOException {
-
- doPost(request, response);
-
- }
-
- public void doPost(HttpServletRequest request, HttpServletResponse response)
-
- throws ServletException, IOException {
-
- response.setContentType("text/html");
-
- PrintWriter out = response.getWriter();
-
- out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional //EN\">");
-
- out.println("<HTML>");
-
- out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");
-
- out.println(" <BODY>");
-
- if (emf != null) {
-
-
-
- EntityManager entityManager = emf.createEntityManager();
-
- try {
-
- Query query = entityManager
-
- .createQuery("SELECT c FROM CustomerEO c");
-
- List<CustomerEO> result = query.getResultList();
-
- for (CustomerEO c : result) {
-
- System.out.println(c.getId() + "," + c.getName());
-
- }
-
- } finally {
-
-
-
- entityManager.close();
-
- }
-
- }
-
- out.println(" </BODY>");
-
- out.println("</HTML>");
-
- out.flush();
-
- out.close();
-
- }
-
- }