第一個 JPA(容器管理)


若 您以Java EE容器來管理JPA相關資源,則您可以使用資源注入的方式取得EntityManager,並可由容器來為您管理Persistence Context,每個EntityManager會關聯至一個Persistence Context,容器會管理Persistence Context的存活範圍,像是Transaction-scoped或Extended-scoped,Persistence Context管理一組Entity,這些進階議題之後介紹。

以下的例子使用Session Facade模式,在一個Stateful Session Bean注入EntityManager,Servlet客戶端利用Session Bean來進行操作,首先定義Stateful Session Bean:
  • EntitySessionRemote.java
package onlyfun.caterpillar;

import javax.ejb.Remote;

@Remote
public interface EntitySessionRemote {
public void save(User user);
public User findById(Long id);
public void clearup();
}

  • EntitySessionBean.java
package onlyfun.caterpillar;

import javax.ejb.*;
import javax.persistence.*;

@Stateful
public class EntitySessionBean implements EntitySessionRemote {
@PersistenceContext(unitName="sample")
private EntityManager entityManager;

public void save(User user) {
entityManager.persist(user);
}

public User findById(Long id) {
User user = entityManager.find(User.class, id);
return user;
}

@Remove
public void clearup() {
}
}

 在這邊使用@PersistenceContext注入EntityManager實例,這樣的方式取得的EntityManager,稱之 為Container-Managed EntityManager,其中unitName屬性指定了persistence.xml中的Persistence Context名稱,Persistence Context預設為Transaction-scoped,也就是在方法開始前會啟始交易,結束後停止交易,Persistence Context的存活範圍在交易之間。

注意到,EntityManager不是Thread-safe,所以要注意在多執行緒下共用存取的同步問題,在這邊使用Stateful Session Bean,讓每個客戶端取得一個Session Bean實例,並獨自使用一個EntityManager。

在persistence.xml的部份:
  • persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="sample" transaction-type="JTA">
<jta-data-source>jdbc/sample</jta-data-source>
<properties>
<property name="toplink.ddl-generation"
value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>

注意到transaction-type屬性為JTA,並透過<jta-data-source>設置容器端管理的Data Source之JNDI名稱,這必須在容器端先定好,JDBC資源與Connection Pool等。

接著來寫個Servlet客戶端:
  • UserAdmin.java
package onlyfun.caterpillar;

import java.io.*;

import java.util.logging.*;
import javax.naming.*;
import javax.rmi.PortableRemoteObject;
import javax.servlet.*;
import javax.servlet.http.*;

public class UserAdmin extends HttpServlet {
protected void processRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

String name = request.getParameter("name");
String age = request.getParameter("age");

User user = new User();
user.setName(name);
user.setAge(new Long(Long.parseLong(age)));

response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
InitialContext context = new InitialContext();
Object obj = context.lookup(
"onlyfun.caterpillar.EntitySessionRemote");
EntitySessionRemote entitySession = (EntitySessionRemote)
PortableRemoteObject.narrow(obj, EntitySessionRemote.class);

entitySession.save(user);
user = entitySession.findById(new Long(2));

out.println(user.getName() + " saved...");

entitySession.clearup();
} catch (NamingException ex) {
Logger.getLogger(UserAdmin.class.getName())
.log(Level.SEVERE, null, ex);
} finally {
out.close();
}
}

protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
}

您可以使用請求參數name與age指定要儲存的使用者名稱與年紀,程式中透過JNDI Lookup來查找Stateful Session Bean,並操作對應的方法來儲存或取得使用者的資料。