2008年7月29日 星期二
JPA 應用實例 <3> @NamedQuery
在之前的實例中,我們知道採用HQL語法執行查詢的一般用法如下:
public ProductItem findProductItemBySku(String sku) { |
Hql 查詢語句分別寫在不同 method 的作法是常見也容易理解的,基本上也沒有甚麼大問題,但hql 語法分散在各個不同的方法裡容易造成維護上的困難,尤其是 BO 屬性被更動過的時候,開發者調整了 BO 的屬性也需要同步調整對應的 hql 語法,如果沒有同步調整就容易發生疏漏,沒被同步修正的部分就可能會發生狀況。
關於這個問題,一般的解決方案會把hql 查詢語句從 method 層級提升到 class層級去宣告,如此一來,日後要修改時,只要修正物件層級的變數即可,如此便可減少這類的問題發生;
更好的作法是,採用JPA提供的 @NamedQuery annotation去宣告 hql 查詢語句,在 BO 當中宣告hql 查詢語句,日後如果要調整 BO 當中的屬性宣告時,開發人員僅需調整該 BO 的屬性與hql 查詢語句即可,完全不需要調閱其他的程式碼,就可以完成調整動作,因此,可以有效降地維護成本。
@NamedQuery annotation宣告方式與應用範例如下:
// 宣告 @NameQuery 的範例程式 |
// 引用 @NameQuery 的範例程式 |
// 宣告多組 @NameQuery 的範例程式 |
相關資訊:Java Persistence API
2008年7月28日 星期一
UML 案例 <3> 元件圖
圖 1、電子商務 Component Diagram
對很多人來說 Component Diagram 代表的是靜態結構的組成,用來表達的通常是系統、子系統與各個模組之間的關連性,私下跟許多友人探詢後,發現很多人都覺得這類的 UML 圖型較不常用,更意外的是,這麼好用的圖例,很多人根本沒在專案中用過它!這是怎麼一回事?為什麼我每個專案都一定會出現至少一次的圖,卻在其他人的專案中鮮少出現?到底是哪一方面出現了差異?
先解釋一下這張圖好了,圖中有1) 產品結構管理模組、2) 訂單處理模組、3) CRM 資料交換模組、4) 授權處理模組、5) 物流處理模組、6) ERP 資料交換模組等六個模組;每個模組負責的主要功能分別用不同的介面分別定義,例如產品結構管理模組必須具備1) 商品結構定義、2) 替代商品維護、3) 商品資料查詢等三大功能,其他模組也有各自具備的功能,個模組之間的相互串連則透過相依關係去表達,從圖例當中可以看到,訂單處理模組將會使用到產品結構管理模組的商品資料查詢,而物流處理模組則會使用到訂單處理模組所提供的各項功能。
註:由於筆者所使用的免費工具目前尚無法完整表達 UML 2.0 的圖例,所以,採用相依關係,一般而言本相依關係可以換成 UML 2.0 的 Required Interface
從Component Diagram的結構當中,我們可以清楚的了解到各個模組之間的關連性與每一個模組的功能特性,也就是說,這張圖表達了本專案應該要完成的功能與範疇(Scope),各位讀者如果花一點點時間去看這張圖,大概也可以猜出這個專案到底在做甚麼了吧?這就是筆者堅持要畫這張圖的原因之一,讓第一次專案團隊能對專案範疇有正確的概念,也知道類似的功能要放在哪一個模組裡頭。
另外,這張圖對筆者而言還有另外幾個重點,其中一個重點是適度切割功能,如果系統的功能無法被適度切割,我們怎能確保自己不會迷思在一堆的需求當中呢?就像在一團打結的繩索中抽絲剝繭,這是一個過程,不但能確實釐清專案的各種需求,也是確保專案不致迷航的一盞明燈。
第二個重點是,釐清元件之間關聯性,Component Diagram結構當中的相依關係代表者元間之間的依存度,這樣的依存關係也可以作為開發時程的重要參考依據;例如:物流處理模組需要使用訂單處理模組,訂單處理模組需要使用產品結構管理模組,本例的合理的開發次序是 1) 產品結構管理模組、2) 訂單處理模組、3) 物流處理模組,人力與時程安排便可依此為佐證。
在釐清元件之間關聯性的過程中,還可以協助分析設計人員找到系統可能發生異常的部分,下圖是典型的循環相依案例,從圖例中,我們看到訂單處理模組需要CRM 資料交換模組,CRM 資料交換模組也需要訂單處理模組,如果系統採取這種設計方式將可能導致日後無法抽換其中任何一的模組,因此,設計人員必須解構 & 改進這方面的設計,如此才能提升系統的穩定度並增加日後擴充的彈性。
回到文章剛開始的問題,猜猜這張Component Diagram是在專案執行過程的哪個階段製作的? 答案是一開始的企劃階段,企劃階段製作 Component Diagram的好處很多,希望各位透過本文能有所認識,趕快開始善用這張圖吧!
關於適度切割功能這點,筆者的期待是,適度切割功能達到可重用(Reuse)的地步,可重用的概念一直是軟體設計與開發者期待物件導向能帶來的優勢之一,但是我們怎樣找到可以重用的元件呢?
如何解構循環相依?請各位讀者自行思考囉,日後有空再跟各位分享囉。 ^_^
強力推薦:敏捷溯模(Agile Modeling)
UML 案例 <2> 代理人關係
圖二、代理關係(A)
圖三、代理關係(B)
圖四、代理關係(C)
這篇文章並不是要強調UML或者OOAD,也不希望各位誤以為這裡講的就是標準答案,不同的領域專家或許會有更好的見解!我要強調的是技術解決方案(Technical Solution)是因應需求而生,同樣的需求能衍生出不同的解決方案,有些方案易維護、有些方案可以有效降低建置成本…,了解需求、掌握需求才能提供適切的解決方案。
強力推薦:敏捷溯模(Agile Modeling)
2008年7月25日 星期五
"救火隊"? 原來我們需要的不是救火隊
但我必須告訴各位一個事實, QA/QC 檢測出來的結果都是屬於落後指標, 反映出來的現象就是時間不夠, 客戶抱怨增多, 事實是, 即便是已經取得 CMMI Level 3 在 QA/QC 很有經驗的公司都會碰到類似的問題.
從我得到的資訊研判, XX 公司需要的不是救火隊, 也不是 QA/QC, 而是能夠督促團隊成員練好基本功的"教練", 我指的基本功比較偏重的面向是預防工作, 例如以下問題:
1. 怎樣才能真正做好 SA 的確認?
2. 怎樣才能順利把 SA 結果移交給 SD 並確保溝通無虞?
3. 下一關怎樣承先啟後?
因此, 我要推薦一個觀念 "測試優先", 這觀念非常貼近實務, 多少可以替 貴公司帶來經濟效益, 希望能幫上點忙. 關於 SA 的預防, 我會問以下問題 :
1. 客戶是否已經確認相關需求?
2. 客戶將怎樣確認並驗證需求已經被滿足?
3. 測試的情境與步驟為何?
以上問題, 若可以在每一個需求討論階段就被記錄下來並獲得確認, 到 SD 階段就會相對容易承接.
關於 SD ... 呢? "測試優先" 的觀念依然適用
相關資訊:Test Driven Development(TDD)
eXtreme Programming
今天花了一個下午的時間, 總算把我經驗中的 XP 給整理好了, 個人認為, XP 是很務實也很實用的, 在我過往的專案有幾次的機會都有用到 XP, 不過, 大多都是民間專案, 政府標案則因為一堆的限制因素(要做一堆文件), 而無法拓展, 從經驗中, 最有效益的專案也大多都是採用類似 XP 的方式推展, 因此,
強烈建議正在推廣"節能減碳"的政府機關能夠換個思維, 採用 XP, 減少浪費
衍生閱讀:Test Driven Development(TDD)
相關資訊:Modifiability: Or is there Design in Agility?
2008年7月22日 星期二
JPA 應用實例 <1>
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]鍵
7. 設定完成後,點選[Finish]鍵即完成JPA 專案建立。剛建立的 JPA 專案會包括下圖當中的程式庫與目錄,其中 \src\META_INF 目錄底下會產生persistence.xml 與 orm.xml 這兩個設定檔,這兩個設定檔案是JPA 的重要設定,將於後續章節逐一說明。
待續...JPA 應用實例 <2>
JPA 應用實例 <2>
第二步:撰寫JPA基本維護程式
撰寫 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; } } |
利用 Annotations 宣告 JPA 管理的 Entity ,相關 Annotations 的用法其實跟採用XML 宣告的 OR Mapping 非常類似,只不過Annotations 是直接寫在 Bean 裡頭,相應的宣告及案例如下:
Annotations | 說明與案例 |
@Entity | 宣告為一般的Entity |
@Id | 宣告 Entity Bean 對應到永續層(persistence) 的primary key |
JPA Annotations 方式除了由開法者自行輸入之外,也可以透過 Eclipse 的 UI 來設定。
開啟 JPA Structure 與 JPA Detail 兩個視景,如以下圖例,從圖例當中我們可以看到,當我們編輯 Java Bean 程式時,對應的 JPA Structure 視景當中可以看到對應的屬性(attributes),同時 JPA Details 可以看到可用的 JPA Annotations 設定清單,也就是說,我們可以點選JPA Structure當中的屬性然後在JPA Details當中設定適當的值即可。
以下圖例是透過JPA Structure & JPA Details設定的簡單案例,操作步驟說明如下:
JPA Structure當中點選Category 類別名稱,將可以看到JPA Detail 當中出現對應的設定選項,於 Map AS 下拉選項當中選定Entity,該設定會立即反應回對應的程式碼當中,並出現正確的JPA Annotations 宣告 @Entity
於JPA Structure當中點選Category 類別的 id 屬性,可以看到JPA Detail 當中出現對應的設定選項,於 Map AS 下拉選項當中選定Id,JPA Annotations 宣告 @Id會立即反應回對應的程式碼當,注意到,JPA Structure 視景當中看到的 id 屬性會出現一把鑰匙,表示為 primary key。
於JPA Structure當中點選Category 類別的其他屬性,可以看到JPA Detail 當中出現對應的設定選項,於 Map AS 下拉選項當中選定預設的Basic,並可於 Name 下拉選單中直接輸入該屬性對應到永續層的欄位名稱 ,這邊要注意的是,有些JPA Annotations Tag 並未出現在JPA Detail當中,例如 length,這部分就需要手動指定了。
設定完基本的 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> |
現在要著手開始寫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; } |
介面定義完成後,開發者便可以著手開發核心商業邏輯了,基於 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; } } |
執行測試程式,為了簡單起見,本例是採用 main方法撰寫測試程式,這邊要提醒各位的是,為了完整與方便日後測試,建議各位還是採用 JUnit 撰寫測試程式較為妥當,至於JUnit 測試程式該如何撰寫,還請各位另行參閱相關的文章。
範例程式如下:
/** * @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")); } |
執行範例程式時,在 Console 當中應該會看到以下的錯誤訊息,原因在於資料庫定義與相關的資料表並未設定或者出使劃完成所致,因此,我們須要回頭把資料庫相關設定寫到 META-INFpersistence.xml設定檔之中。
META-INFpersistence.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="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> |
撰寫更多的商業邏輯程式,JPA 的 Persistence 主要是使用 Entity Manager 去執行一般的增、刪、改、查動作,相關的範例逐一說明如下。
設計標準介面,例如:查詢、新增、修改與刪除等介面,以下是本實例當中開立的部分案例。
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); 其他… |
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; |
新增或者修改範例:若需要修改紀錄,建議採用 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; |
刪除範例:若需要修改刪除紀錄時,請使用 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(); |
一般查詢範例:除了用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>