2009年2月26日 星期四
20090221 課程回饋
學習心得:
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 設定問題
答案是在設定檔案當中加上一行 <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
包括:
1. 單一物件
2. 一對一關聯
3. 一對多關聯
4. 多對多關聯
5. 繼承關係
相關資訊:Test Driven Development(TDD)
2008年10月21日 星期二
Hibernate Entity Manager / ORM
包括:
1. 單一物件
2. 一對一關聯
3. 一對多關聯
4. 多對多關聯
透過案例展示, 方便大家日後查詢之用, 強調一下, 寫好的程式必須"通過測試才能算是真正完成"
需要完整的範例程式? 連同你的建議寄送到我的信箱! 歡迎各位提供建議 ^_^
相關資訊:Test Driven Development(TDD)
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月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>