顯示具有 JPA 標籤的文章。 顯示所有文章
顯示具有 JPA 標籤的文章。 顯示所有文章

2009年2月26日 星期四

20090221 課程回饋

2009/02/21 JPA(Java Presistence API)課程後,節錄以下回饋資訊

學習心得:
1. JPA 技術 免去過去繁瑣的 hbm.xml 的設定 所造成 手誤,快速增加開發速度。
2. ORM 的實踐 必須 完全放棄 ERM 的設計思維 還有使用的設計技巧 這些會造成ORM 設計上得錯誤。
3. Persistance 建立的 EntiyManagerFactory instance object 有 thread safe 的設計,但是它所建立的 instance object 本身並沒有 thread Safe 的設計,這容易遭成應用上的疏忽。
4. Unit test 提高軟體的品質。不是靠最後辛苦的亡羊補牢,而是 開發過程中 經過設計且綿延的測試才可產出有品質的軟體。
5. 這次上課有發現 JPA 的一些功能,如:getReference 提升效能的方法,另外對於JPA 的 Annotation 感覺有滿有用的,如果有自訂錯誤訊息的功能就更好了
6. 在這次的課程中 我有提出 客戶端老是喜歡變動資料庫的動作,比如說倒資料進DB,這部分在有id的Table且有關聯的狀況下 會死得很慘,如果客戶的資料庫不得不變動的情況下或許所上的課程會不敷使用

改善建議:
1. 多點討論!類似像 head first 系列的書列出的問題 相當有意思
2. 這次課程比較接近給用過hibernate並且要轉JPA的人來上 若是沒有用過 hibernate的人來上課 可能會不易聽懂
3. 這次的課程 雖然很清楚的講了JPA的實作, 但是對於開發大型專案的時候較容易遇到效能的問題 如果能夠有多一些多層次資料的提升效能的寫法,將會是有很大的幫助(PS 這點僅供參考)

學習方向:(近來發現的學習方向,如果學會了,對我們這個團隊應該有幫助)
1. JPA 和 Annotation 的應用 AOP 的開發概念。
2. Unit test 專案中使用的測試案例 與實際UI建立後 所作的測試 其 前期測試與後期測試 誤差太高。
3. 因為我本身會 hibernate 的技術,學完JPA後 覺得JPA是對於hibernate的upgrade,又加深對於資料庫存取的了解,也多了一點對Annotation的應用方面更多的思考,若是再有新專案開發,希望能夠將 JPA 搭配使用Annotation來檢核Business Object,如果再利用Annotation把多國語言也做出來,一定會讓開發更是快速且簡單

2008年12月26日 星期五

JPA - persistence.xml 設定問題

題目是沒設定在 persistence.xml 當中的 class,系統還是會自動 load 並產生對應的 DB 欄位,要怎樣排除呢?
答案是在設定檔案當中加上一行 <exclude-unlisted-classes>,參閱以下範例

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
    <persistence-unit name="sample">
        <class>sample.Bean1</class>
        <exclude-unlisted-classes/>
    </persistence-unit>
</persistence>

2008年10月28日 星期二

Hibernate Entity Manager / JPA Annotation

ORM 除了用 xml 設定方式之外, 例一種方式就是用 @annotation, 這裡一樣透過案例展示, 直接示範幾種常用的關聯
包括:
1. 單一物件
2. 一對一關聯
3. 一對多關聯
4. 多對多關聯
5. 繼承關係



相關資訊:Test Driven Development(TDD)

2008年10月21日 星期二

Hibernate Entity Manager / ORM

使用過 hibernate 開發程式的人, 都會碰到 ORM 的問題, 這份簡報整理了常用的關聯案例,

包括:
1. 單一物件
2. 一對一關聯
3. 一對多關聯
4. 多對多關聯

透過案例展示, 方便大家日後查詢之用, 強調一下, 寫好的程式必須"通過測試才能算是真正完成"



需要完整的範例程式? 連同你的建議寄送到我的信箱! 歡迎各位提供建議 ^_^

相關資訊:Test Driven Development(TDD)

2008年7月29日 星期二

JPA 應用實例 <3> @NamedQuery

JPA @NamedQuery 查詢範例

在之前的實例中,我們知道採用HQL語法執行查詢的一般用法如下:

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;
}

Hql 查詢語句分別寫在不同 method 的作法是常見也容易理解的,基本上也沒有甚麼大問題,但hql 語法分散在各個不同的方法裡容易造成維護上的困難,尤其是 BO 屬性被更動過的時候,開發者調整了 BO 的屬性也需要同步調整對應的 hql 語法,如果沒有同步調整就容易發生疏漏,沒被同步修正的部分就可能會發生狀況。

關於這個問題,一般的解決方案會把hql 查詢語句從 method 層級提升到 class層級去宣告,如此一來,日後要修改時,只要修正物件層級的變數即可,如此便可減少這類的問題發生;

更好的作法是,採用JPA提供的 @NamedQuery annotation去宣告 hql 查詢語句,在 BO 當中宣告hql 查詢語句,日後如果要調整 BO 當中的屬性宣告時,開發人員僅需調整該 BO 的屬性與hql 查詢語句即可,完全不需要調閱其他的程式碼,就可以完成調整動作,因此,可以有效降地維護成本。

@NamedQuery annotation宣告方式與應用範例如下:

// 宣告 @NameQuery 的範例程式
@NamedQuery(name="findProductItemBySku", query="from ProductItem where sku=:sku ")
@Entity
public class ProductItem {

}

// 引用 @NameQuery 的範例程式
public ProductItem findProductItemBySku(String sku) {
ProductItem result = null;
// 取得 EntityManager
EntityManager em = entityManagerFactory.createEntityManager();
try {
// 依照產品編號 (SKU) 找到對應的產品基本資料
result = (ProductItem) em.createNamedQuery("findProductItemBySku").setParameter("sku", sku).getSingleResult();
} catch (NoResultException err) {
} finally {
em.close();
}
return result;
}

果開發者要在同一個 BO 當中宣告多組 @NameQuery 時,請利用@NamedQueries annotation 加以宣告,範例如下:

// 宣告多組 @NameQuery 的範例程式
@NamedQueries({
@NamedQuery(name="findProductItemBySku", query="from ProductItem where sku=:sku "),
@NamedQuery(name="findProductItemByName", query="from ProductItem where name=:name ")
})
@Entity
public class ProductItem {

}


相關資訊:Java Persistence API

2008年7月22日 星期二

JPA 應用實例 <1>

Java Persistence API 基本概念

JPA是EJB 3.0規格當中的要點之一,JPA 規格主要定義了物件資料儲存到永續層的標準,其中包括了類似 Hibernate 的 OR Mapping 與資料存取核心,由於 JDK 5.0 引入了 Annotations 的概念,因此,OR Mapping 的方是自然也提供了 XML 定義檔之外的另一個選項;

JPA 同時擴充了資料存取核心(Core),除了簡化資料增、刪、改、查的過程外,更重要的是,透過 Entity Manager 可以讓記憶體當中的資料與永續層資料之間的同步維護更加容易。

接下來的案例將逐步介紹開發 JPA 程式的過程,希望藉由案例的演練,能引發大夥對 JPA 的興趣,同時帶領各位進入 JPA 的聖殿。

前置作業

由於本案例主要是採用 hibernate 提供的 Entity Manager進行開發,因此,需要請各位自行到 hibernate 網站下載相關的 JAR 檔案
網址:http://www.hibernate.org/
需要下載的元件:
1. Hibernate Core
2. Hibernate EntityManager
3. Hibernate Annotations

本案例存取的資料庫主要為 MS-SQL Server 2005,因此,需要請各位相對應的 JDBC Driver,本案例採用 jTDS JDBC Driver
網址:http://jtds.sourceforge.net/

建立資料庫-請先行進入 MS-SQL Server 2005 建立本次實務案例需要用到的資料庫,並新增相對應的登入帳號與資料庫存取權限,以下是基本設定參數:
1. 資料庫名稱:JPADB
2. 建立登入帳號:jpauser,建立帳號請同時取消勾選項對應的密碼原則,並請設定使用者對應的資料庫為 JPADB同時擁有 db_owner 的權限。

註:因為本案例主要是用來說明與示範用途,因此,建立帳號時給予比較大的權限,關於 MS-SQL 2005的權限管理細節,請自行閱讀相關的文章。

案例 Domain Model

上圖是本次JPA即將要時做的基本模型,從模型當中,我們可以知道有產品 (ProductItem) 與商品類型 (Category) 這兩個 Class,產品與商品類型之間有著多對多的關係,也就是說,產品可以歸屬到多種商品類型,而單一商品類型也可以同時包含多種產品。

有了以上基本認識後,本案例將從維護(增刪改查)產品或者商品類型的基本作業逐步進展到整體維護的JPA應用方法;


第一步:建立 JPA 專案

1. 使用 Eclipse 建立新的專案,[File]/[New]/[Project]
2. 選定 JPA Project之後按[下一步]鍵。
3. 輸入專案名稱,按[下一步]鍵。
4. 選定採用的元件版本,其中JDK 務必選擇 5.0 以上的版本
5. 於 Connection 下拉選單中選擇或自行設定資料庫連線方式,若要自行設定資料庫連線,請點選 [Add connection…] 超連結 。

  • 選擇[Generic JDBC Connection],按[下一步]鍵
  • 輸入JPA DB Connection 後,按[下一步]鍵
  • 建立並輸入資料庫連線資訊
    URL:jdbc:jtds:sqlserver://localhost:1433/JPADB
    註一:請參閱前置作業章節找到對應的資料庫連線資訊
    註二:相對應的 JDBC Driver 請先下載並存放到適當的路徑底下。
  • 設定資料輸入後,請點選[Test Connection]鍵進行資料庫連線測試,待測試通過後才按[Finish]鍵
6. 於 JPA implementation library下拉選單中選擇或新增使用者自訂的 JPA 程式庫。若要自行設定 JPA 程式庫,請點選 [Configure default JPA implementation library…] 超連結 。
7. 設定完成後,點選[Finish]鍵即完成JPA 專案建立。剛建立的 JPA 專案會包括下圖當中的程式庫與目錄,其中 \src\META_INF 目錄底下會產生persistence.xml 與 orm.xml 這兩個設定檔,這兩個設定檔案是JPA 的重要設定,將於後續章節逐一說明。

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

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>