2008年7月22日 星期二

JPA 應用實例 <2>

第二步:撰寫JPA基本維護程式

  1. 撰寫 ProductItem Category 兩支Bean程式,寫好getter/setter & constructor

/**

* 產品基本資料

* @author TommyKao

*/

public class ProductItem {

/**

* 產品系統編號

*/

private long id;

/**

* 產品編號

*/

private String sku;

/**

* 產品名稱

*/

private String name;

/**

* 產品描述

*/

private String description;

/**

* 以下是 constructor 的宣告

*/

protected ProductItem() {

super();

}

public ProductItem(String sku, String name) {

super();

this.sku = sku;

this.name = name;

this.description = "";

}

/**

* 以下是 getter/setter 的宣告

*/

public long getId() {

return id;

}

protected void setId(long id) {

this.id = id;

}

public String getSku() {

return sku;

}

protected void setSku(String sku) {

this.sku = sku;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getDescription() {

return description;

}

public void setDescription(String description) {

this.description = description;

}

}

  1. 利用 Annotations 宣告 JPA 管理的 Entity ,相關 Annotations 的用法其實跟採用XML 宣告的 OR Mapping 非常類似,只不過Annotations 是直接寫在 Bean 裡頭,相應的宣告及案例如下:


Annotations

說明與案例

@Entity

宣告為一般的Entity

@Entity
public class ProductItem {

}

@Id

宣告 Entity Bean 對應到永續層(persistence) primary key

public class ProductItem {
@Id
private long id;

}

  1. JPA Annotations 方式除了由開法者自行輸入之外,也可以透過 Eclipse UI 來設定。

    1. 開啟 JPA Structure JPA Detail 兩個視景,如以下圖例,從圖例當中我們可以看到,當我們編輯 Java Bean 程式時,對應的 JPA Structure 視景當中可以看到對應的屬性(attributes),同時 JPA Details 可以看到可用的 JPA Annotations 設定清單,也就是說,我們可以點選JPA Structure當中的屬性然後在JPA Details當中設定適當的值即可。

    1. 以下圖例是透過JPA Structure & JPA Details設定的簡單案例,操作步驟說明如下:

      1. JPA Structure當中點選Category 類別名稱,將可以看到JPA Detail 當中出現對應的設定選項,於 Map AS 下拉選項當中選定Entity,該設定會立即反應回對應的程式碼當中,並出現正確的JPA Annotations 宣告 @Entity

      1. JPA Structure當中點選Category 類別的 id 屬性,可以看到JPA Detail 當中出現對應的設定選項,於 Map AS 下拉選項當中選定IdJPA Annotations 宣告 @Id會立即反應回對應的程式碼當,注意到,JPA Structure 視景當中看到的 id 屬性會出現一把鑰匙,表示為 primary key

      1. JPA Structure當中點選Category 類別的其他屬性,可以看到JPA Detail 當中出現對應的設定選項,於 Map AS 下拉選項當中選定預設的Basic,並可於 Name 下拉選單中直接輸入該屬性對應到永續層的欄位名稱 ,這邊要注意的是,有些JPA Annotations Tag 並未出現在JPA Detail當中,例如 length,這部分就需要手動指定了。

  1. 設定完基本的 JPA Annotations 之後,請把 META-INFpersistence.xml 打開,內容當中可以看到前面設定的 Entity 已經設定包含在<class> … </class> 標籤中了。

<?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="JPASample1">

<class>sample.jpa.Category</class>

<class>sample.jpa.ProductItem</class>

</persistence-unit>

</persistence>

  1. 現在要著手開始寫MVC 當中的 Model 層服務,在撰寫 Model 層服務之前,建議各位先行定義開發的界面程式。
    以下是本次演練要開發的服務

/**

* ICatalogService 提供的標準服務介面定義

* @author TommyKao

*/

public interface ICatalogService {

/**

* 依照商品分類的 primary key 找到對應的分類資料

* @param id

* @return

*/

public Category findCategoryByID(long id);

/**

* 儲存商品分類資料

* @param category

* @return

* @throws Exception

*/

public Category saveCategory(Category category) throws Exception;

}

  1. 介面定義完成後,開發者便可以著手開發核心商業邏輯了,基於 JPA 的核心商業邏輯分段說明如下:

public class CatalogService implements ICatalogService {

/**

* Singleton pattern

*/

private static CatalogService instance = new CatalogService();

/**

* 永續層的進入點 EntityManagerFactory

*/

private static EntityManagerFactory entityManagerFactory;

private CatalogService() {

super();

if (entityManagerFactory==null) {

try {

// 取得 EntityManagerFactory

entityManagerFactory = Persistence

.createEntityManagerFactory("JPASample1");

} catch (Throwable ex) {

throw new ExceptionInInitializerError(ex);

}

}

}

public static CatalogService getInstance() {

return instance;

}

/**

* 依照商品分類的 primary key 找到對應的分類資料

* @param id

* @return

*/

public Category findCategoryByID(long id) {

Category result = null;

try {

// 取得 EntityManager

EntityManager em = entityManagerFactory.createEntityManager();

// 依照 primary key 找到對應的商品分類

result = em.find(Category.class, id);

return result;

} catch (NoResultException err) {

return null;

}

}

/**

* 儲存商品分類資料

* @param category

* @return

* @throws Exception

*/

public Category saveCategory(Category category) throws Exception {

Category persistNode = null;

if (category != null) {

persistNode = this.findCategoryByID(category.getId());

// 取得 EntityManager

EntityManager em = entityManagerFactory.createEntityManager();

EntityTransaction etx = em.getTransaction();

etx.begin();

if (persistNode != null) {

persistNode.setCaption(category.getCaption());

// 永續層已經有資料, Merge 方式執行修改(update)動作

category = em.merge(persistNode);

} else {

em.persist(category);

}

etx.commit();

em.close();

persistNode = category;

}

return persistNode;

}

}

  1. 執行測試程式,為了簡單起見,本例是採用 main方法撰寫測試程式,這邊要提醒各位的是,為了完整與方便日後測試,建議各位還是採用 JUnit 撰寫測試程式較為妥當,至於JUnit 測試程式該如何撰寫,還請各位另行參閱相關的文章。

    1. 範例程式如下:

/**

* @param args

*/

public static void main(String[] args) {

ICatalogService service = CatalogService.getInstance();

Category category = new Category("JAVA JPA");

try {

category = service.saveCategory(category);

System.out.println("saveCategory Caption : " +(category!=null?category.getCaption():"DATA NOT FOUND"));

} catch (Exception e1) {

e1.printStackTrace();

}

Category result = service.findCategoryByID(1);

System.out.println("findCategory Caption : " +(result!=null?result.getCaption():"DATA NOT FOUND"));

}


    1. 執行範例程式時,在 Console 當中應該會看到以下的錯誤訊息,原因在於資料庫定義與相關的資料表並未設定或者出使劃完成所致,因此,我們須要回頭把資料庫相關設定寫到 META-INFpersistence.xml設定檔之中。


    1. META-INFpersistence.xml設定檔需補充設定資料庫連線方式如下。

    2. 設定完成後,再次執行測試程式,將可得到正確結果如下圖。

<?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="JPASample1">

<class>sample.jpa.Category</class>

<class>sample.jpa.ProductItem</class>

<properties>

<!-- 資料庫類型 -->

<property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect"/>

<!-- 資料庫 JDBC 驅動程式 -->

<property name="hibernate.connection.driver_class" value="net.sourceforge.jtds.jdbc.Driver" />

<!-- 資料庫連線 URL -->

<property name="hibernate.connection.url" value="jdbc:jtds:sqlserver://localhost:1433/jpadb" />

<!-- 資料庫連線使用者帳號 -->

<property name="hibernate.connection.username" value="jpauser" />

<!-- 資料庫連線使用者密碼 -->

<property name="hibernate.connection.password" value="jpauser" />

<!-- 資料庫 Scheam 有差異時的處理方式 -->

<property name="hibernate.hbm2ddl.auto" value="update"/>

</properties>

</persistence-unit>

</persistence>

  1. 撰寫更多的商業邏輯程式,JPA Persistence 主要是使用 Entity Manager 去執行一般的增、刪、改、查動作,相關的範例逐一說明如下。

    1. 設計標準介面,例如:查詢、新增、修改與刪除等介面,以下是本實例當中開立的部分案例。

public interface ICatalogService {

/**

* [PK查詢範例]依照商品分類的 primary key 找到對應的分類資料

*/

public Category findCategoryByID(long id);

/**

* [新增與修改範例]儲存商品分類資料

*/

public Category saveCategory(Category category) throws Exception;

/**

* [刪除範例]刪除商品分類資料

*/

public void removeCategoryByID(long id);

/**

* [一般查詢範例]依照產品編號 (SKU) 找到對應的產品基本資料

*/

public ProductItem findProductItemBySku(String sku);

其他
}

    1. PK查詢範例:Entity Manager 有一個 find() method 可以直接用物件的 Primary Key 到永續層查詢對應的物件,實際用法與範例如下:

/**

* [PK查詢範例]依照商品分類的 primary key 找到對應的分類資料

*/

public Category findCategoryByID(long id) {

EntityManager em = entityManagerFactory.createEntityManager();

// 依照 primary key 找到對應的商品分類

Category result = em.find(Category.class, id);

return result;
}

    1. 新增或者修改範例:若需要修改紀錄,建議採用 merge() method

/**

* [新增與修改範例]儲存商品分類資料

*/

public Category saveCategory(Category category) Exception {

if (category != null) {

Category persistNode = this.findCategoryByID(category.getId());

EntityManager em = entityManagerFactory.createEntityManager();

EntityTransaction etx = em.getTransaction();

etx.begin();

if (persistNode != null) {

persistNode.setCaption(category.getCaption());

// 永續層已經有資料, Merge 方式執行修改(update)動作

category = em.merge(persistNode);

} else {

// 新的資料資料, Persist 方式執行新增(insert)動作

em.persist(category);

}

etx.commit();

em.close();

}

return persistNode;
}

    1. 刪除範例:若需要修改刪除紀錄時,請使用 remove() method 去整理各項分類

/**

* [刪除範例]刪除商品分類資料

*/

public void removeCategoryByID(long id) {

// 取得 EntityManager

EntityManager em = entityManagerFactory.createEntityManager();

EntityTransaction etx = em.getTransaction();

etx.begin();

Category category = em.find(Category.class, id);

if (category!=null) {

em.remove(category);

}

etx.commit();

em.close();
}

    1. 一般查詢範例:除了用PK查詢之外,若開發者希望依照其他的條件去查詢時,可採用HQL語法,HQL SQL 語法非常類似,差異在於 SQL 的對象是資料表與欄位,而 HQL 的對象則是類別與屬性,以下為一般查詢的範例程式。

/**

* [一般查詢範例]依照產品編號 (SKU) 找到對應的產品基本資料

*/

public ProductItem findProductItemBySku(String sku) {

String hql = "from ProductItem where sku=:sku ";

// 取得 EntityManager

EntityManager em = entityManagerFactory.createEntityManager();

Query query = em.createQuery(hql); // 建立查詢語法

query.setParameter("sku", sku); // 填入查詢條件

// 依照產品編號 (SKU) 找到對應的產品基本資料

ProductItem result = (ProductItem) query.getSingleResult();

return result;

}

/**

* [一般查詢範例]依照產品編號 (SKU) 找到對應的產品基本資料

*/

public ProductItem findProductItemBySku(String sku) {

String hql = "from ProductItem where sku=:sku ";

// 取得 EntityManager

EntityManager em = entityManagerFactory.createEntityManager();

// 相同的語法可以濃縮成以下這行

ProductItem result = (ProductItem) em.createQuery(hql)

.setParameter("sku", sku).getSingleResult();

return result;

}




待續...JPA 應用實例 <3>

2 則留言:

Mendel Lee 提到...

我編輯我的JavaBean,可是在JPA Structure 與 JPA Detail這二個視圖中,並沒有看到如大大所說的樣子,是空白的,不知道是怎麼原因,想請大大可否把你的這個專案打包放上網路上分享。謝謝。

匿名 提到...

圖片不見了