Google App Engine簡單地說就是一個支援J2EE和python運行環境的免費主機空間和一個*.appspot.com的次層網域。最大的優點是免費,無論是CPU時還是頻寬對於普通的小型網站都是綽綽有餘的。但是GAE有很多很多的限制,主要跟大家分享一下我在GAE上做J2EE項目遇到的各種限制吧。
首先最突出的就是沙箱的運行環境,所謂沙箱,就是一個獨立的Runspace,在這個空間裡無論執行什麼樣的代碼都不會對其他沙箱和整個系統造成影響。一般來說沙箱的普遍限制是不可以訪問本地檔案,這對GAE也是一樣的,不能寫檔案,但是可以讀隨著應用程式一起上傳的檔案。所有的資料都要用資料庫來儲存和讀取。
說起資料庫,這是GAE讓人感覺最不習慣的一個地方。GAE的資料庫不是真正意義的關聯式資料庫,是為了配合分布式GFS而用BigTable實現的類似資料庫的東東。不是關聯式資料庫,自然不能支援完整的SQL和平常在關聯式資料庫中非常自然的一些操作。GAE上對資料的訪問必須用JDO(Java Data Object)和JPA來訪問,推薦使用JDO,比JPA方便不少。首先最大的限制是SQL語句只支援SELECT *和SELECT _KEY_,即只能是select操作,而且必須select全部欄位或者select主鍵。不支援任何的insert、delete和update操作。但是可以通過JDO提供的持久層操作來實現此資料的CRUD(Create, Retrive, Update, Delete)。使用GAE平台不需要顯式的定義表的scheme,只需要直接定義Data Objects。具體的定義方法可以參考GAE文檔,需要注意的是主鍵不能是int,可以用Long(注意L是大寫的)。給出一個實現了CRUD的簡單DataService類:
public class ArticleServiceImpl implements ArticleService {<br /> protected ArticleServiceImpl() {<br /> }</p><p> public void create(Article article) {<br /> PersistenceManager pm = PMF.get().getPersistenceManager();<br /> try {<br /> pm.makePersistent(article);<br /> } finally {<br /> pm.close();<br /> }<br /> }</p><p> public Article retrieve(String id) {<br /> PersistenceManager pm = PMF.get().getPersistenceManager();<br /> try {<br /> Article article = pm.getObjectById(Article.class, id);<br /> pm.detachCopy(article);<br /> return article;<br /> } catch (Exception e) {<br /> return null;<br /> } finally {<br /> pm.close();<br /> }<br /> }</p><p> public void update(Article article) {<br /> PersistenceManager pm = PMF.get().getPersistenceManager();<br /> try {<br /> Article original = pm.getObjectById(Article.class, article.getId());<br /> original.setCategoryid(article.getCategoryid());<br /> original.setCategoryindex(article.getCategoryindex());<br /> original.setContent(article.getContent());<br /> original.setIndex(article.getIndex());<br /> original.setPostdate(article.getPostdate());<br /> original.setTitle(article.getTitle());<br /> pm.makePersistent(original);<br /> } finally {<br /> pm.close();<br /> }<br /> }</p><p> public void delete(String id) {<br /> PersistenceManager pm = PMF.get().getPersistenceManager();<br /> try {<br /> Article article = pm.getObjectById(Article.class, id);<br /> pm.deletePersistent(article);<br /> } finally {<br /> pm.close();<br /> }<br /> }<br />}<br />
逐個來分析,Create方法最簡單,直接把傳進來的DO交給持久層就行了。Retrive使用主鍵從持久層擷取資料,但是這個資料還是屬於持久層管理著的,如果關閉了PersistentManager,那麼這個對象也就不存在了,所以要把這個對象detach出來。就是讓其從持久層脫離出來,脫離出來的方法就是做一個深度複製,也就是detachCopy方法。Update方法尤其要注意,可以看到雖然傳進來一個新的資料對象,但是不能直接把這個對象makePersistent了,否則會拋出異常。正確的操作步驟是先用新的資料對象的主鍵從持久層擷取此對象,然後把新對象的資料賦給剛從持久層抓出來的舊對象,最後儲存舊對象。舊的資料對象一直生存在持久層上,新的對象的功能只是提供資料而已。Delete方法也比較簡單,直接deletePersistent就行了。
另一個非常麻煩的問題是GAE資料的limit(a, b)查詢是把從0到b的對象全部查出來,然後丟棄前a個,返回後面的b-a個對象。並且,a和b的限制都是小於1000,也就是說如果用limit最多能夠到的資料是2000以內。這樣就引出另一個問題,如何對大量的資料做分頁?我解決方案是給每一個資料添加index欄位來表示其在資料庫中的位置,分頁可以直接在index上使用條件查詢來擷取。對於可能發生的資料增減和修改,每次都需要維護index的連貫性,雖然付出不小的代價,但是仍然比limit的效率高,而且能突破2000條資料的限制。
最後一個值得說說的問題是在Date類型欄位上的查詢,我嘗試用用各種字串的日期格式來試圖在Date欄位上做條件查詢,都失敗了。無法可想的我想出一個更好的方案:使用long儲存Date類型。因為Java中每一個Date對象都可以用getTime方法返回一個唯一的long。也就是說每一個Date值都可以和一個long值唯一對應,並且互相轉化。那麼在資料中把資料按照long來存放,查詢就簡單多了:
public List<Comment> listComments(int days) {<br /> PersistenceManager pm = PMF.get().getPersistenceManager();<br /> Date date = new Date();<br /> Calendar calendar = Calendar.getInstance();<br /> calendar.setTime(date);<br /> calendar.add(Calendar.DATE, -days);<br /> date = calendar.getTime();<br /> Query query = pm.newQuery(Comment.class);<br /> query.setFilter(String.format("postdate >= %s", date.getTime()));<br /> query.setOrdering("postdate desc");<br /> try {<br /> List<Comment> comments = (List<Comment>) query.execute();<br /> pm.detachCopyAll(comments);<br /> return comments;<br /> } finally {<br /> query.closeAll();<br /> pm.close();<br /> }<br />}<br />
上面的例子是尋找最近幾天內的評論的一個資料庫查詢操作,注意setFilter方法和detachCopyAll方法。
對於Date類型,還有一點要提,GAE上的時間是美國時間,我們是中國時間,又有一個時區轉換的問題存在。我的解決方案是全部的Date的資料存放區、邏輯操作都按照GAE上的預設時區來,只在輸出Date為字串的時候做轉換:
public String getLocalDateTimeStr(Date date) {<br /> DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");<br /> dateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8")); // all date object is display in GMT+8 time zone<br /> return dateFormat.format(date)<br />}<br />
以上是我在GAE上做這個部落格的一些經驗,在這裡分享一下。難免有錯,歡迎指正。
原文連結:http://hzqtcblog.appspot.com/article.jsp?articleid=dsntvawxgl