第一個 JPA(單機客戶端)


Java的物件導向模型與關聯資料庫模型之間有相當程度的不匹配,而一些物件與資料庫資料同步、更新,也為常見的Java永續儲存(Persistence)問題,在過去,Object/Relational Mapping(ORM)有 JBoss Hibernate、Oracle TopLink等解決方案,而JPA為吸收這些方案的經驗,所製訂出的Java永續儲存標準。

使用JPA,底層可以使用不同廠商的ORM實作,而介面則是JPA的標準,若您使用NetBeans+Glassfish,則預設的底層實作為TopLink,JBoss的工具其底層實作則為Hibernate,若您偏好Hibernate,則可以再參考  Hibernate AnnotationsHibernate 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;
    ....

}

其它對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()方法來撤回交易。