Java的物件導向模型與關聯資料庫模型之間有相當程度的不匹配,而一些物件與資料庫資料同步、更新,也為常見的Java永續儲存(Persistence)問題,在過去,Object/Relational Mapping(ORM)有 JBoss Hibernate、Oracle TopLink等解決方案,而JPA為吸收這些方案的經驗,所製訂出的Java永續儲存標準。
使用JPA,底層可以使用不同廠商的ORM實作,而介面則是JPA的標準,若您使用NetBeans+Glassfish,則預設的底層實作為TopLink,JBoss的工具其底層實作則為Hibernate,若您偏好Hibernate,則可以再參考 Hibernate Annotations、Hibernate EntityManager 內容,了解Hibernate如何支援JPA。
以下先示範如何於非容器環境中使用JPA,假設您在demo資料庫中個T_USER表格,而您打算寫個User類別來與之對應:
- User.java
package onlyfun.caterpillar;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name="T_USER")
public class User implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    private Long age;
    public void setId(Long id) {  this.id = id;  }
    public Long getId() { return id;  }
    public String getName() {  return name;  }
    public void setName(String name) {  this.name = name;  }
    public Long getAge() {  return age;  }
    public void setAge(Long age) {  this.age = age;  }
   
}User中的每個資料成員對應至T_USER中的每個欄位,即id成員對應至id欄位,name成員對應至name欄位,age成員對應age欄位。
為了成為JPA的Entity類別,您必須使用@Entity加以標註,@Table標示這個Entity類別對應的資料表格,若類別名稱與表格名稱相同,則可以省略,預設會將類別名稱對應至同名的表格,Entity類別必須實作Serializable介面。
每個Entity類別必須有獨一無二的識別屬性,並與資料表格的主鍵相對應,您要使用@Id標註在資料成員或Getter方法上,@GeneratedValue讓您可以選擇主鍵的產生策略,在這邊利用資料庫本身的自動產生策略,由底層的資料庫來提供。
若成員名稱與表格欄位名稱一樣,則會自動對應,若不同,則可以使用@Column來指定欄位名稱,例如:
@Entity
@Table(name="T_USER")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
      
@Column(name=C_NAME)
private String name;
      
@Column(name=C_AGE)
private Long age;
....
      
}
      
      
      @Table(name="T_USER")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name=C_NAME)
private String name;
@Column(name=C_AGE)
private Long age;
....
}
其它對Entity的一些要求是:
- 類別必須是public
- 不可以是final類別,不可以有final方法
- 要有public或protected的無引數建構子,或預設建構子
 
- 資料成員不可以是public
- 沒有finalize方法
為了JPA必須設定資料庫連結與底層實作的一些細節,您要在META-INF下撰寫一個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="demo" transaction-type="RESOURCE_LOCAL">
    <provider>oracle.toplink.essentials.PersistenceProvider</provider>
    <class>onlyfun.caterpillar.User</class>
    <properties>
      <property name="toplink.jdbc.user" 
                value="caterpillar"/>
      <property name="toplink.jdbc.password" 
                value="123456"/>
      <property name="toplink.jdbc.url" 
                value="jdbc:derby://localhost:1527/demo"/>
      <property name="toplink.jdbc.driver" 
                value="org.apache.derby.jdbc.ClientDriver"/>
      <property name="toplink.ddl-generation" 
                value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>主要就是設定一些資料庫 JDBC URL、使用者名稱、密碼等資訊,一個persistence.xml中可以設定多個Persistence Unit,每個Persistence Unit可當作一個資料庫連結設定,<persistence-unit>的name名稱即作為Persistence Unit的識別名稱。
在這邊所使用的是TopLink實作,"toplink.ddl-generation"用來設定當JPA程式EntityManagerFactory建立時,自動刪除資料表格並重建新的資料表格,這可用在測試時期,方便您不用親自作這些重置表格的動作。
接著,您要建立EntityManagerFactory,EntityManagerFactory內含設定資訊,負責管理 EntityManager,而這樣的方式所取得的EntityManager,稱之為Application-Managed EntityManager。
您可以如下撰寫一個JPAUtil類別:
- JPAUtil.java
package onlyfun.caterpillar;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class JPAUtil {
    private static EntityManagerFactory entityManagerFactory;
    static {
        try {
            entityManagerFactory = 
                Persistence.createEntityManagerFactory("demo");
        }
        catch(Throwable ex) {
            throw new ExceptionInInitializerError(ex);
        }
    }
 
    public static EntityManagerFactory getEntityManagerFactory() {
        return entityManagerFactory;
    }
 
    public static void shutdown() {
        getEntityManagerFactory().close();
    }
}JPAUtil方便您取得EntityManager物件,Entity物件的生命週期、與資料表格的對應、資料庫的存取,都與EntityManager息息相關,例如您可以撰寫以下的程式,取得EntityManager進行User物件的儲存或是查詢:
- Main.java
package onlyfun.caterpillar;
import javax.persistence.*;
public class Main {
    public static void main(String[] args) {
        User user = new User();
        user.setName("Justin Lin");
        user.setAge(new Long(30));
 
        save(user);
        user = findById(new Long(2));
        
        System.out.println(user.getName());
 
        JPAUtil.shutdown();
    }
    private static void save(User user) {
        EntityManager entityManager = 
            JPAUtil.getEntityManagerFactory().createEntityManager();
        EntityTransaction etx = entityManager.getTransaction();
        etx.begin();
        entityManager.persist(user);
        etx.commit();
        entityManager.close();        
    }
    
    private static User findById(Long id) {
        EntityManager entityManager = 
            JPAUtil.getEntityManagerFactory().createEntityManager();
        EntityTransaction etx = entityManager.getTransaction();
        etx.begin();
        User user = entityManager.find(User.class, id);
        etx.commit();
        entityManager.close();              
        return user;
    }
}取得EntityManager後,可透過getTransaction()取得EntityTransaction,EntityTransaction 負責管理交易,您可以透過EntityManger的persist()方法來儲存User物件,EntityManager會自動將對應的成員儲存至對 應的資料表格欄位,在這邊則若透過EntityManager的find()方法,指定主鍵來查找資料並封裝為User物件,基本上所有的EntityManager操作,要在交易中完成,但find()可以不用在交易中完成,只不過若不在交易中使用find()方法,則查找回來的Entity將立刻不在EntityManager的管理之中(也就是處於Detached狀態)。
若交易過程中發生錯誤,可以捕捉例外,執行EntityTransaction的rollback()方法來撤回交易。

