Google Code 提供下列語言介面: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
在可擴充的網路應用程式中儲存資料需要一些小技巧。使用者可以在指定時間內,與數十個網頁伺服器進行互動,而且使用者的下一個要求可能會送給不同的網頁伺服器,而不是處理前一個要求的伺服器。所有網頁伺服器需要資料互動,這些資料也是散佈在多部電腦中,而這些電腦也可能位於世界的各個角落。
有了「Google 應用服務引擎」之後,您就不需要擔心上述問題。「應用服務引擎」的基礎架構會透過簡單的 API 處理資料的散佈、複製以及負載平衡,而您會獲得強大的查詢引擎與交易功能。
「應用服務引擎」資料存放區是「應用服務引擎」提供的多種服務之一,它提供兩種 API:標準 API 和低階 API。 需要的話,您可以使用標準 API,輕鬆地將應用程式移植到其他的主機環境和資料庫技術。標準 API 可以將您的應用程式從「應用服務引擎」服務「解除聯結」。「應用服務引擎」也支援可直接提供服務功能的低階 API。您可以使用低階 API 實作新的介面卡介面,或在您的應用程式中直接使用 API。
「應用服務引擎」支援兩種不同的資料存放區 API 標準:Java Data Object (JDO) 和 Java Persistence API (JPA)。 這些介面是由 DataNucleus Access Platform (數種 Java 持續性標準的開放原始碼實作) 透過「應用服務引擎」資料存放區的介面卡所提供。
在訪客留言板中,我們將透過 JDO 介面擷取與張貼使用者所留下的訊息。
Access Platform 需要一個設定檔,指示它使用「應用服務引擎」資料存放區做為 JDO 實作的後端。在最終的 WAR 中,這個檔案的名稱為 jdoconfig.xml,存放在 war/WEB-INF/classes/META-INF/ 目錄中。
如果您使用的是 Eclipse,這個檔案會建立為 src/META-INF/jdoconfig.xml。這個檔案會在您建置專案時自動複製到 war/WEB-INF/classes/META-INF/。
如果您使用的不是 Eclipse,您可以直接建立 war/WEB-INF/classes/META-INF/ 目錄,或者由您的建置程序建立它,再從另一個位置複製設定檔。「使用 Apache Ant」所描述的 Ant 建置指令碼會從 src/META-INF/ 複製這個檔案。
jdoconfig.xml 檔案應該具備下列內容:
<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">
<persistence-manager-factory name="transactions-optional">
<property name="javax.jdo.PersistenceManagerFactoryClass"
value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
<property name="javax.jdo.option.ConnectionURL" value="appengine"/>
<property name="javax.jdo.option.NontransactionalRead" value="true"/>
<property name="javax.jdo.option.NontransactionalWrite" value="true"/>
<property name="javax.jdo.option.RetainValues" value="true"/>
<property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
</persistence-manager-factory>
</jdoconfig>
建立 JDO 類別時,您可以使用 Java 註解來描述在資料存放區中儲存實例的方式,以及從資料存放區擷取時重新建立實例的方式。Access Platform 會使用編譯後的處理步驟 (DataNucleus 稱為「強化」類別),將類別連結至實作。
如果您使用的是 Eclipse 和「Google 外掛程式」,外掛程式會在建置程序中自動執行 JDO 類別強化。
如果您使用的是「使用 Apache Ant」所描述的 Ant 建置指令碼,這個建置指令碼包括必要的強化步驟。
如需 JDO 類別強化的詳細資訊,請參閱「使用 JDO」。
JDO 可讓您透過任何 JDO 相容的介面卡 (例如 DataNucleus Access Platform),在資料存放區中儲存 Java 物件 (有時稱為 Plain Old Java Object 或 POJO)。「應用服務引擎 SDK」包含適用於「應用服務引擎」資料存放區的 Access Platform 外掛程式。這表示您可以儲存您在「應用服務引擎」資料存放區所定義的類別之實例,並使用 JDO API 以物件形式擷取這些實例。 您可以透過 Java 註解,指示 JDO 如何儲存與重新建構類別的實例。
讓我們建立 Greeting 類別來代表張貼在訪客留言板上的個別訊息。
在 guestbook 套件中建立 Greeting 新類別。(非 Eclipse 使用者,請在 src/guestbook/ 中建立 Greeting.java 檔案)。請為來源檔案設定下列內容:
package guestbook;
import java.util.Date;
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;
import com.google.appengine.api.users.User;
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Greeting {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
@Persistent
private User author;
@Persistent
private String content;
@Persistent
private Date date;
public Greeting(User author, String content, Date date) {
this.author = author;
this.content = content;
this.date = date;
}
public Long getId() {
return id;
}
public User getAuthor() {
return author;
}
public String getContent() {
return content;
}
public Date getDate() {
return date;
}
public void setAuthor(User author) {
this.author = author;
}
public void setContent(String content) {
this.content = content;
}
public void setDate(Date date) {
this.date = date;
}
}
這個簡單類別定義了 3 個訊息的屬性:author、content 和 date。這三個私有欄位會透過 @Persistent 註解,以告知 DataNucleus 在「應用服務引擎」資料存放區中將它們儲存為物件的屬性。
這個類別定義了屬性的 getter 和 setter,但是僅供應用程式使用,JDO 並不需要。
這個類別還定義了名為 id 的欄位,這是一個包含 @Persistent 和 @PrimaryKey 兩個註解的 Long。 「應用服務引擎」資料存放區具備實體金鑰概念,而且可以在物件上以不同方式代表金鑰。在這個案例中,金鑰欄位為長整數,而且會在儲存物件時自動設為唯一的數字 ID。
如需 JDO 註解的詳細資訊,請參閱「定義資料類別」。
每個使用資料存放區的要求都會建立新的 PersistenceManager 類別實例。 這個步驟是透過 PersistenceManagerFactory 類別的實例完成。
PersistenceManagerFactory 實例需要一些時間初始化,幸好您的應用程式只需要一個實例。這個實例可以儲存在靜態變數中,供多個要求和多個類別使用。一個簡單的執行方式是,透過靜態實例建立單一包裝函式類別。
在 guestbook 套件 (位於 src/guestbook/ 目錄的 PMF.java 的檔案) 中,建立 PMF 新類別,然後為它設定下列內容:
package guestbook;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;
public final class PMF {
private static final PersistenceManagerFactory pmfInstance =
JDOHelper.getPersistenceManagerFactory("transactions-optional");
private PMF() {}
public static PersistenceManagerFactory get() {
return pmfInstance;
}
}
有了 DataNucleus 和 Greeting 類別,表單處理邏輯即可在資料存放區中儲存新的訊息。
按照所述指示,根據下列內容編輯 src/guestbook/SignGuestbookServlet.java:
package guestbook;
import java.io.IOException;
import java.util.Date;
import java.util.logging.Logger;
import javax.jdo.PersistenceManager;
import javax.servlet.http.*;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import guestbook.Greeting;
import guestbook.PMF;
public class SignGuestbookServlet extends HttpServlet {
private static final Logger log = Logger.getLogger(SignGuestbookServlet.class.getName());
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
String content = req.getParameter("content");
Date date = new Date();
Greeting greeting = new Greeting(user, content, date);
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
pm.makePersistent(greeting);
} finally {
pm.close();
}
resp.sendRedirect("/intl/zh-TW/guestbook.jsp");
}
}
這個程式碼會呼叫建構函式,藉此建立新的 Greeting 實例。如要將實例儲存至資料存放區,它會使用 PersistenceManagerFactory 建立 PersistenceManager,然後將實例傳送至 PersistenceManager 的 makePersistent() 方法。註解和位元組碼強化會從這裡接手。makePersistent() 傳回之後,新物件將儲存到資料存放區。
JDO 標準定義了持續性物件的查詢機制,稱為 JDOQL。您可以使用 JDOQL 查詢「應用服務引擎」資料存放區中的實體,再以 JDO 強化物件形式擷取結果。
在這個範例中,我們會將查詢程式碼直接寫入 guestbook.jsp,讓事情簡單些。對於較大型的應用程式,建議您將查詢邏輯委派給另一個類別。
依據下列內容,編輯 war/guestbook.jsp 並添加粗體標示的程式碼:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.List" %>
<%@ page import="javax.jdo.PersistenceManager" %>
<%@ page import="com.google.appengine.api.users.User" %>
<%@ page import="com.google.appengine.api.users.UserService" %>
<%@ page import="com.google.appengine.api.users.UserServiceFactory" %>
<%@ page import="guestbook.Greeting" %>
<%@ page import="guestbook.PMF" %>
<html>
<body>
<%
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
if (user != null) {
%>
<p>Hello, <%= user.getNickname() %>! (You can
<a href="<%= userService.createLogoutURL(request.getRequestURI()) %>">sign out</a>.)</p>
<%
} else {
%>
<p>Hello!
<a href="<%= userService.createLoginURL(request.getRequestURI()) %>">Sign in</a>
to include your name with greetings you post.</p>
<%
}
%>
<%
PersistenceManager pm = PMF.get().getPersistenceManager();
String query = "select from " + Greeting.class.getName();
List<Greeting> greetings = (List<Greeting>) pm.newQuery(query).execute();
if (greetings.isEmpty()) {
%>
<p>The guestbook has no messages.</p>
<%
} else {
for (Greeting g : greetings) {
if (g.getAuthor() == null) {
%>
<p>An anonymous person wrote:</p>
<%
} else {
%>
<p><b><%= g.getAuthor().getNickname() %></b> wrote:</p>
<%
}
%>
<blockquote><%= g.getContent() %></blockquote>
<%
}
}
pm.close();
%>
<form action="/sign" method="post">
<div><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Post Greeting" /></div>
</form>
</body>
</html>
如要準備查詢,請呼叫 PersistenceManager 實例的 newQuery() 方法,並採用查詢文字做為字串。這個方法會傳回查詢物件。查詢物件的 execute() 方法會執行查詢,然後傳回適當類型的結果物件的 List<>。查詢字串必須包含要查詢的類別完整名稱,包括套件名稱。
重新建置專案,並重新啟動伺服器。瀏覽 http://localhost:8080/。輸入一則訊息並提交。訊息會出現在表單上。輸入另一則訊息並提交。兩則訊息會同時顯示。嘗試使用這些連結登出與登入,並嘗試在登入與未登入的狀況下提交訊息。
提示:在真實世界的應用程式中,建議您在顯示使用者所提交的內容 (例如此應用程式中的訊息) 時,逸出 HTML 字元。JavaServer Pages Standard Tag Library (JSTL) 包含可逸出 HTML 字元的常式。「應用服務引擎」內含 JSTL (以及其他 JSP 相關的執行階段 JAR),因此您不需要在應用程式中納入它們。如要在 http://java.sun.com/jsp/jstl/functions 標記程式庫尋找 escapeXml 函式,請參閱「Sun J2EE 1.4 教學課程」的詳細資訊。
我們的訪客留言板目前會顯示所有張貼到系統的訊息。它也可以按照建立順序顯示訊息。當訪客留言板擁有眾多訊息時,為求實用性,可以只顯示近期訊息並將最近的訊息置頂。我們可以透過調整資料存放區的查詢,完成這個步驟。
您可以使用 JDOQL (一種可擷取資料物件的類 SQL 查詢語言),透過 JDO 介面執行查詢。在我們的 JSP 頁面中,下列程式碼會定義 JDOQL 查詢字串:
String query = "select from " + Greeting.class.getName();
換言之,JDOQL 查詢字串如下:
select from guestbook.Greeting
這個查詢會向資料存放區要求目前已儲存的 Greeting 類別的所有實例。
查詢可以納入條件,並限制結果物件的屬性必須符合這些條件,藉此篩選結果。例如,如果只想擷取 author 屬性為 alfred@example.com 的 Greeting 物件,您可以使用下列查詢:
select from guestbook.Greeting where author == 'alfred@example.com'
查詢可以依據屬性值,指定結果的傳回順序。如要使用張貼順序的相反順序 (從最新到最舊) 擷取所有 Greeting 物件,您可以使用下列查詢:
select from guestbook.Greeting order by date desc
查詢可以限制只傳回某個範圍內的結果。如果只要擷取最近的 5 則訊息,您可以搭配使用 order by 和 range,如下所示:
select from guestbook.Greeting order by date desc range 0,5
讓我們為訪客留言板應用程式執行這個步驟。在 guestbook.jsp 中,使用下列內容取代 query 定義:
String query = "select from " + Greeting.class.getName() + " order by date desc range 0,5";
在訪客留言板張貼超過 5 則的訊息。只有最近的 5 則訊息會以逆時間順序 (最新的訊息在最前面) 顯示。
如需查詢和 JDOQL 的詳細資訊,請參閱「查詢和索引」。
每個網路應用程式都會透過範本或其他機制,從應用程式碼動態傳回產生的 HTML。大部分網路應用程式也需要服務靜態內容,例如影像、CSS 樣式表或 JavaScript 檔案。為了效率起見,「應用服務引擎」會將應用程式原始碼和資料檔案以不同方式處理。讓我們為這個應用程式建立一個 CSS 樣式表 (靜態檔案)。
繼續瀏覽「使用靜態檔案」一節。