Google App Engine的Java持久性與數據存儲
本文繼續介紹Google App Engine for Java。這篇講述持久性和關系。
Google App Engine for Java 力求為可伸縮的 Web 應用程序成功地編寫一個持久層,可這個目標的達成情況又如何呢?在本文中,我將概述 App Engine for Java 的持久性框架,從而結束本系列文章。該框架以 Java Data Objects(JDO)和 Java Persistence API(JPA)為基礎。盡管在剛剛出現時前景良好,但是 App Engine 的基于 Java 的持久性目前存在一些嚴重的缺陷,我將對此進行解釋和演示。您將學習 App Engine for Java 持久性是如何運作以及有著哪些挑戰,還將學習在使用面向 Java 開發人員的 Google 云平臺時,您具有哪些持久性選擇。
在閱讀本文并遍覽這些示例時,您要牢記這樣的事實:現在的 App Engine for Java 是一個預覽 版?;?Java 的持久性目前也許并不是您所希望或者需要的全部,可能并且應該會在未來發生變化。現如今,使用 App Engine for Java 進行可伸縮的、數據密集型的 Java 應用程序開發不合適膽小者或者保守派,這就是我在撰寫本文時所學到的。這更像跳入了游泳池的最深處:看不到任何救生員,項目要沉下去還是往前游,取決于您自己。
注意,本文中的示例應用程序以 第 2 部分 中開發的聯系人管理應用程序為基礎。您需要構建該應用程序,確保它是可運行的,這樣才能繼續學習本文的示例。
基礎知識和抽象泄漏(leaky abstraction)
與原始的 Google App Engine 一樣,App Engine for Java 依靠 Google 的內部基礎設施,實現可伸縮的應用程序開發的 Big Three:分布、復制和負載均衡。由于使用的是 Google 基礎設施,因此所有神奇的地方大都發生在后臺,可以通過 App Engine for Java 的基于標準的 API 獲得。數據存儲接口是以 JDO 和 JPA 為基礎的,而它們自身又是以開源的 DataNucleus 項目為基礎。AppEngine for Java 還提供了一個低級別的適配器 API,用來直接處理基于 Google 的 BigTable 實現的 App Engine for Java 數據存儲(要了解更多有關 BigTable 的信息,請參見 第 1 部分)。
然而,App Engine for Java 數據持久性并不像純 Google App Engine 中的持久性那樣簡單。由于 BigTable 不是一個關系數據庫,JDO 和 JPA 的接口出現了一些抽象泄漏。例如,在 App Engine for Java 中,您無法進行那些執行連接的查詢。您可以在 JPA 和 JDO 間設置關系,但它們只能用來持久化關系。并且在持久化對象時,如果它們在相同的實體群中,那么它們只能被持久化到相同的原子事務中。根據慣例,具有所有權的關系位于與父類相同的實體群中。相反,不具有所有權的關系可以在不同的實體群中。
重新考慮數據規范化
要使用 App Engine 的可伸縮的數據存儲,需要重新考慮有關規范化數據的優點的教導。當然,如果您在真實的環境中工作了足夠長的時間,那么,您可能已經為了追求性能而犧牲過規范化了。區別在于,在處理 App Engine 數據存儲時,您必須盡早且經常進行反規范化。反規范化 不再是一個忌諱的字眼,相反,它是一個設計工具,您可以把它應用在 App Engine for Java 應用程序的許多方面。
當您嘗試把為 RDBMS 編寫的應用程序移植到 App Engine for Java 時,App Engine for Java 的持久性泄漏的主要缺陷就會顯露出來。App Engine for Java 數據存儲并不是關系數據庫的臨時替代物,因此,要把您對 App Engine for Java 所做的工作移植到 RDBMS 端口并不容易。采用現有的模式并把它移植到數據存儲中,這種場景則更為少見。如果您決定把一個遺留的 Java 企業應用程序移植到 App 引擎中,建議您要小心謹慎,并進行備份分析。Google App Engine 是一個針對專門為它設計的應用程序的平臺。Google App Engine for Java 支持 JDO 和 JPA,這使得這些應用程序能夠被移植回更傳統的、未進行規范化的企業應用程序。
關系的問題
App Engine for Java 目前的預覽版的另外一個缺點是它對關系的處理。為了創建關系,現在您必須對 JDO 使用 App Engine for Java 特有的擴展。假設鍵是在 BigTable 的工件的基礎上生成 — 也就是說,“主鍵” 將父對象鍵編碼到其所有子鍵中 — 您將不得不在一個非關系數據庫中管理數據。另外一個限制是持久化數據。如果您使用非標準的 AppEngine for Java Key 類,事情將會變得復雜。首先,把模型移植到 RDBMS 時,如何使用非標準 Key? 其次,由于無法使用 GWT 引擎轉換 Key 類,因此,任何使用這個類的模型對象都無法被作為 GWT 應用程序的一部分進行使用。
當然,撰寫這篇文章時,Google App Engine for Java 還是純粹的預覽模式,沒有到發布的最佳時間。學習 JDO 中的關系文檔(很少,而且包含一些不完整的示例)時,這點就變得顯而易見了。
App Engine for Java 開發包提供了一系列的示例程序。許多示例都使用 JDO,沒有一個使用 JPA。這些示例中沒有一個示例(包括一個名為 jdoexamples 的示例)演示了關系,即使是簡單的關系。相反,所有的示例都只使用一個對象把數據保存到數據存儲中。Google App Engine for Java 討論組 充斥著有關如何使簡單關系起作用的問題,但卻鮮有答案。很顯然,有些開發人員有辦法使其起作用,但是實現起來都很困難,而且遇到了一些復雜情況。
App Engine for Java 中的關系的底線是,無需從 JDO 或 JPA 獲得大量支持就能夠管理它們。 Google 的 BigTable 是一種已經經過檢驗的技術,可用來生成可伸縮的應用程序,然而,您還可以在此基礎上進行構建。在 BigTable 上進行構建,您就不必處理還不完善的 API 層面。另一方面,您只要處理一個較低級別的 API。
#p#
App Engine for Java 中的 Java Data Objects
把傳統的 Java 應用程序移植到 App Engine for Java 中,甚至是給出關系挑戰,這些可能都沒有什么意義,然而,持久性場景還是存在的,這時使用這個平臺就有意義了。我將使用一個可行的示例來結束本文,您將體驗 App Engine for Java 持久性是如何工作的。我們將以 第 2 部分 中建立的聯系人管理應用程序為基礎,介紹如何添加支持,以使用 App Engine for Java 數據存儲工具持久化 Contact 對象。
在前面的文章中,您創建了一個簡單的 GWT GUI,對 Contact 對象進行 CRUD 操作。您定義了簡單的接口,如清單 1 所示:
package gaej.example.contact.server;
import java.util.List;
import gaej.example.contact.client.Contact;
public interface ContactDAO {
void addContact(Contact contact);
void removeContact(Contact contact);
void updateContact(Contact contact);
List<Contact> listContacts();
} |
接下來,創建一個模擬版本,與內存集合中的數據進行交互,如清單 2 所示:
清單 2. 模擬 DAO 的 ContactDAOMock
package gaej.example.contact.server;
import gaej.example.contact.client.Contact;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class ContactDAOMock implements ContactDAO {
Map<String, Contact> map = new LinkedHashMap<String, Contact>();
{
map.put("rhightower@mammatus.com", new Contact("Rick Hightower",
"rhightower@mammatus.com", "520-555-1212"));
map.put("scott@mammatus.com", new Contact("Scott Fauerbach",
"scott@mammatus.com", "520-555-1213"));
map.put("bob@mammatus.com", new Contact("Bob Dean",
"bob@mammatus.com", "520-555-1214"));
}
public void addContact(Contact contact) {
String email = contact.getEmail();
map.put(email, contact);
}
public List<Contact> listContacts() {
return Collections.unmodifiableList(new ArrayList<Contact>(map.values()));
}
public void removeContact(Contact contact) {
map.remove(contact.getEmail());
}
public void updateContact(Contact contact) {
map.put(contact.getEmail(), contact);
}
} |
現在,使用與 Google App Engine 數據存儲交互的應用程序替換模擬實現,看看會發生什么。在這個示例中,您將使用 JDO 持久化 Contact 類。使用 Google Eclipse Plugin 編寫的應用程序已經擁有了使用 JDO 所需的所有庫。它還包含了一個 jdoconfig.xml 文件,因此,一旦對 Contact 類進行了注釋,您就已經準備好開始使用 JDO。
清單 3 顯示擴展后的 ContactDAO 接口,可使用 JDO API 進行持久化、查詢、更新和刪除對象:
package gaej.example.contact.server;
import gaej.example.contact.client.Contact;
import java.util.List;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
public class ContactJdoDAO implements ContactDAO {
private static final PersistenceManagerFactory pmfInstance = JDOHelper
.getPersistenceManagerFactory("transactions-optional");
public static PersistenceManagerFactory getPersistenceManagerFactory() {
return pmfInstance;
}
public void addContact(Contact contact) {
PersistenceManager pm = getPersistenceManagerFactory()
.getPersistenceManager();
try {
pm.makePersistent(contact);
} finally {
pm.close();
}
}
@SuppressWarnings("unchecked")
public List<Contact> listContacts() {
PersistenceManager pm = getPersistenceManagerFactory()
.getPersistenceManager();
String query = "select from " + Contact.class.getName();
return (List<Contact>) pm.newQuery(query).execute();
}
public void removeContact(Contact contact) {
PersistenceManager pm = getPersistenceManagerFactory()
.getPersistenceManager();
try {
pm.currentTransaction().begin();
// We don't have a reference to the selected Product.
// So we have to look it up first,
contact = pm.getObjectById(Contact.class, contact.getId());
pm.deletePersistent(contact);
pm.currentTransaction().commit();
} catch (Exception ex) {
pm.currentTransaction().rollback();
throw new RuntimeException(ex);
} finally {
pm.close();
}
}
public void updateContact(Contact contact) {
PersistenceManager pm = getPersistenceManagerFactory()
.getPersistenceManager();
String name = contact.getName();
String phone = contact.getPhone();
String email = contact.getEmail();
try {
pm.currentTransaction().begin();
// We don't have a reference to the selected Product.
// So we have to look it up first,
contact = pm.getObjectById(Contact.class, contact.getId());
contact.setName(name);
contact.setPhone(phone);
contact.setEmail(email);
pm.makePersistent(contact);
pm.currentTransaction().commit();
} catch (Exception ex) {
pm.currentTransaction().rollback();
throw new RuntimeException(ex);
} finally {
pm.close();
}
}
}
|
#p#
逐一比對方法
現在,考慮一下使用清單 3 中的每個方法時發生的情況。您將會發現,方法的名字可能是新的,但它們的動作大部分情況下都應該感到熟悉。
首先,為了獲取 PersistenceManager,創建了一個靜態的 PersistenceManagerFactory。如果您以前使用過 JPA,PersistenceManager 與 JPA 中的 EntityManager 很相似。如果您使用過 Hibernate,PersistenceManager 與 Hibernate Session 很相似?;旧?,PersistenceManager 是 JDO 持久性系統的主接口。它代表了與數據庫的會話。getPersistenceManagerFactory() 方法返回靜態初始化的 PersistenceManagerFactory,如清單 4 所示:
private static final PersistenceManagerFactory pmfInstance = JDOHelper
.getPersistenceManagerFactory("transactions-optional");
public static PersistenceManagerFactory getPersistenceManagerFactory() {
return pmfInstance;
}
|
addContact() 方法把新的聯系人添加到數據存儲中。為了做到這點,需要創建一個 PersistenceManager 實例,然后,調用 PersistenceManager 的 makePersistence() 方法。makePersistence() 方法采用臨時的 Contact 對象(用戶將在 GWT GUI 中填充),并且使其成為一個持久的對象。所有這些如清單 5 所示:
清單 5. addContact()
public void addContact(Contact contact) {
PersistenceManager pm = getPersistenceManagerFactory()
.getPersistenceManager();
try {
pm.makePersistent(contact);
} finally {
pm.close();
}
}
|
注意在清單 5 中,persistenceManager 是如何被封入在 finally 塊中。這確保能夠把與 persistenceManager 關聯的資源清除干凈。
如清單 6 所示,listContact() 方法從它所查找的 persistenceManager 中創建一個查詢對象。它調用了 execute() 方法,從數據存儲中返回 Contact 列表。
清單 6. listContact()
@SuppressWarnings("unchecked")
public List<Contact> listContacts() {
PersistenceManager pm = getPersistenceManagerFactory()
.getPersistenceManager();
String query = "select from " + Contact.class.getName();
return (List<Contact>) pm.newQuery(query).execute();
}
|
在從數據存儲中刪除聯系人之前,removeContact() 通過 ID 查找聯系人,如清單 7 所示。它必須這么做,而不僅僅是把聯系人直接刪除,這是因為來自 GWT GUI 的 Contact 對 JDO 一無所知。在刪除前,您必須獲得與 PersistenceManager 緩存關聯的 Contact。
清單 7. removeContact()
public void removeContact(Contact contact) {
PersistenceManager pm = getPersistenceManagerFactory()
.getPersistenceManager();
try {
pm.currentTransaction().begin();
// We don't have a reference to the selected Product.
// So we have to look it up first,
contact = pm.getObjectById(Contact.class, contact.getId());
pm.deletePersistent(contact);
pm.currentTransaction().commit();
} catch (Exception ex) {
pm.currentTransaction().rollback();
throw new RuntimeException(ex);
} finally {
pm.close();
}
}
|
清單 8 中的 updateContact() 方法與 removeContact() 方法類似,用來查找 Contact。然后,updateContact() 方法從 Contact 中復制屬性。這些屬性被當作實參(Argument)傳送到 Contact,后者由持久性管理器查找。使用 PersistenceManager 檢查所查找的對象發生的變化。如果對象發生了變化,當事務進行提交時,這些變化會被 PersistenceManager 刷新到數據庫。
清單 8. updateContact()
public void updateContact(Contact contact) {
PersistenceManager pm = getPersistenceManagerFactory()
.getPersistenceManager();
String name = contact.getName();
String phone = contact.getPhone();
String email = contact.getEmail();
try {
pm.currentTransaction().begin();
// We don't have a reference to the selected Product.
// So we have to look it up first,
contact = pm.getObjectById(Contact.class, contact.getId());
contact.setName(name);
contact.setPhone(phone);
contact.setEmail(email);
pm.makePersistent(contact);
pm.currentTransaction().commit();
} catch (Exception ex) {
pm.currentTransaction().rollback();
throw new RuntimeException(ex);
} finally {
pm.close();
}
}
|
#p#
對象持久性注釋
為了使 Contact 能夠具有持久性,必須把它識別為一個具有 @PersistenceCapable 注釋的可持久性對象。然后,需要對它所有的可持久性字段進行注釋,如清單 9 所示:
清單 9. Contact 具有可持久性
package gaej.example.contact.client;
import java.io.Serializable;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Contact implements Serializable {
private static final long serialVersionUID = 1L;
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
@Persistent
private String name;
@Persistent
private String email;
@Persistent
private String phone;
public Contact() {
}
public Contact(String name, String email, String phone) {
super();
this.name = name;
this.email = email;
this.phone = phone;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
|
通過面向對象的編程和接口設計原則,您只需使用新的 ContactJdoDAO 替代原始的 ContactDAOMock。然后 GWT GUI 無需任何修改就可處理 JDO。
最后,在這種替換中,真正改變 的是 DAO 在服務中被實例化的方式。如清單 10 所示:
清單 10. RemoteServiceServlet
public class ContactServiceImpl extends RemoteServiceServlet implements ContactService {
private static final long serialVersionUID = 1L;
//private ContactDAO contactDAO = new ContactDAOMock();
private ContactDAO contactDAO = new ContactJdoDAO();
...
|
結束語
在這篇由三部分組成的文章中,介紹了 Google App Engine for Java 目前為持久性提供的支持,這是交付可伸縮應用程序的基礎??偟慕Y論令人失望,但是要注意這是一個正在發展中的平臺。為 App Engine for Java 預覽版編寫的應用程序被連接到 App Engine 的持久性基礎設施,即使是用 JDO 或 JPA 編寫。App Engine for Java 預覽版幾乎沒有為它的持久性框架提供任何文檔,而且 App Engine for Java 提供的示例幾乎無法演示即使是最簡單的關系。
即使 JDO 和 JPA 實現已經完全成熟,目前您仍然不可能編寫一個 App Engine for Java 應用程序并輕松地把它移植到一個基于 RDBMS 的企業應用程序。要使移植能夠起作用,至少要編寫大量的代碼。
我希望持久性能隨著時間的推移而成熟起來。如果現在必須使用 App Engine for Java,您可能需要繞過 Java API,直接編寫低級別的 Datastore API。使用 App Engine for Java 平臺是可能的,但是,如果習慣了使用 JPA 和/或 JDO,那么將出現一條學習曲線,因為存在本文前面描述的抽象泄漏,并且目前的功能要么還無法正常運行,要么還沒有進行很好的文檔記錄。




















