Google App Engine for Java,第 3 部分: 持久性和关系--基于 Java 的持久性和 Google App Engine 数据存储

在企业环境中,数据持久性是交付可伸缩应用程序的基础。Rick Hightower 在他撰写的有关 Google App Engine for Java 的系列文章的最后一篇中,介绍了 App Engine 当前基于 Java 的持久性框架。让我们学习一些基础知识,了解为什么当前预览版中的 Java 持久性还未到发布的最佳时间,同时获得一个良好的演示,看看您如何在 App Engine for Java 应用程序中保存数据。注意,您将需要启动并运行来自第2部分的联系人管理应用程序,在此过程中学习如何使用 JDO API 保存、查询、更新和删除 Contact 对象。

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。

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 所示:

清单 1. 简单的 ContactDAO 接口

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 进行持久化、查询、更新和删除对象:

清单 3. 使用 JDO 的 ContactDAO

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(); } }}

逐一比对方法

现在,考虑一下使用清单 3 中的每个方法时发生的情况。您将会发现,方法的名字可能是新的,但它们的动作大部分情况下都应该感到熟悉。

首先,为了获取 PersistenceManager,创建了一个静态的 PersistenceManagerFactory。如果您以前使用过 JPA,PersistenceManager 与 JPA 中的 EntityManager 很相似。如果您使用过 Hibernate,PersistenceManager 与 Hibernate Session 很相似。基本上,PersistenceManager 是 JDO 持久性系统的主接口。它代表了与数据库的会话。getPersistenceManagerFactory() 方法返回静态初始化的 PersistenceManagerFactory,如清单 4 所示:

清单 4. getPersistenceManagerFactory() 返回 PersistenceManagerFactory

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(); }}

对象持久性注释

为了使 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 目前为持久性提供的支持,这是交付可伸缩应用程序的基础。总的结论令人失望,但是要注意这是一个正在发展中的平台。为 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,那么将出现一条学习曲线,因为存在本文前面描述的抽象泄漏,并且目前的功能要么还无法正常运行,要么还没有进行很好的文档记录。

时间: 2015-03-12
Tags: java, nbsp, manager

Google App Engine for Java,第 3 部分: 持久性和关系--基于 Java 的持久性和 Google App Engine 数据存储的相关文章

源代码-基于JAVA的电子商城的设计

问题描述 基于JAVA的电子商城的设计 求基于Java的电子商城设计的源代码,最好是可以直接跑起来的,代码不要太复杂,简单易懂,求大神资源! 解决方案 关于商城系统中商品类别的设计 解决方案二: 看看这个 java开发SHOP电子商城网站平台shop++http://www.zuidaima.com/share/2043180788730880.htm 解决方案三: google里面输入完整下面一行 site:download.csdn.net java 商城 就有一波源代码赶来. 解决方案四:

在应用中加入全文检索功能——基于Java的全文索引引擎Lucene简介

全文检索|索引 内容摘要: Lucene是一个基于Java的全文索引工具包. 基于Java的全文索引引擎Lucene简介:关于作者和Lucene的历史 全文检索的实现:Luene全文索引和数据库索引的比较 中文切分词机制简介:基于词库和自动切分词算法的比较 具体的安装和使用简介:系统结构介绍和演示 Hacking Lucene:简化的查询分析器,删除的实现,定制的排序,应用接口的扩展 从Lucene我们还可以学到什么 基于Java的全文索引/检索引擎--Lucene Lucene不是一个完整的全

基于JAVA技术的搜索引擎的研究与实现

搜索引擎 摘要 网络中的资源非常丰富,但是如何有效的搜索信息却是一件困难的事情.建立搜索引擎就是解决这个问题的最好方法.本文首先详细介绍了基于英特网的搜索引擎的系统结构,然后从网络机器人.索引引擎.Web服务器三个方面进行详细的说明.为了更加深刻的理解这种技术,本人还亲自实现了一个自己的搜索引擎--新闻搜索引擎. 新闻搜索引擎是从指定的Web页面中按照超连接进行解析.搜索,并把搜索到的每条新闻进行索引后加入数据库.然后通过Web服务器接受客户端请求后从索引数据库中搜索出所匹配的新闻. 本人在介绍

基于java语音缓存系统的研究与设计,怎么写毕业论文???是否需要做一个系统

问题描述 基于java语音缓存系统的研究与设计,怎么写毕业论文???是否需要做一个系统 这个东西是什么>??是否需要做出一系统,这个论文怎么写,谁帮写好能通过重谢 解决方案 这个应该是不需要做一个系统的,这并不是一个项目或者安卓的app,并不是偏向实践方向的,而是更偏向理论放心吧.个人理解,具体的建议你问问你的导师吧 解决方案二: 如果你什么都不会,那就胡乱抄抄类似的文章吧google总会用吧.http://www.docin.com/p-65599058.html 解决方案三: butaiqi

面向Java开发人员的Ajax:构建动态的Java应用程序

在 Web 应用程序开发中,页面重载循环是最大的一个使用障碍,对于 Java 开发人员来说也是一个严峻的挑战.在这个系列中,作者 Philip McCarthy 介绍了一种创建动态应用程序体验的开创性方式.Ajax(异步 JavaScript 和 XML)是一种编程技术,它允许为基于 Java 的 Web 应用程序把 Java 技术.XML 和 JavaScript 组合起来,从而打破页面重载的范式. Ajax(即异步 JavaScript 和 XML)是一种 Web 应用程序开发的手段,它采用

基于SWT实现桌面版Google Map

引言 不知道什么时候开始,地图应用已经如此的普及,基于地图的 WEB 应用丰富多 彩.比较著名的有 Google Map.Yahoo Map.Bing Map 等等,其中使用率最高的要属 Google Map,它简单易用,包含丰富的内容,并且具有良好的用户体验.与此同时,地图应用也普及到 桌面应用上,人们试图基于桌面地图开发新应用,用的比较普遍的是 GIS(Geographic Information System),然而 GIS 对比 Google Map 的简单易用.信息丰富,还是有一定的差

基于java nio的memcached客户端

1.xmemcached是什么? xmemcached是基于java nio实现的memcached客户端API. 实际上是基于我实现的一个简单nio框架 http://code.google.com/p/yanf4j/的基础上实现的(目前是基于yanf4j 0.52),核心代码不超过1000行,序列化机制直接挪用spymemcached的Transcoder. 性能方面,在读写简单类型上比之spymemcached还是有差距,在读写比较大的对象(如集合)有效率优势. 当前0.50-beta版本

Lucene:基于Java的全文检索引擎简介

Lucene是一个基于Java的全文索引工具包. 基于Java的全文索引引擎Lucene简介:关于作者和Lucene的历史 全文检索的实现:Luene全文索引和数据库索引的比较 中文切分词机制简介:基于词库和自动切分词算法的比较 具体的安装和使用简介:系统结构介绍和演示 Hacking Lucene:简化的查询分析器,删除的实现,定制的排序,应用接口的扩展 从Lucene我们还可以学到什么 基于Java的全文索引/检索引擎--Lucene Lucene不是一个完整的全文索引应用,而是是一个用Ja

懂java和php来,aes加解密将java版转为php版

问题描述 懂java和php来,aes加解密将java版转为php版 /** * AES加密 * * @param key * 密钥信息 * @param content * 待加密信息 */ public static byte[] encodeAES(byte[] key, byte[] content) throws Exception { // 不是16的倍数的,补足 int base = 16; if (key.length % base != 0) { int groups = ke

基于java的汽车整车仓储信息系统 怎么写啊毕业设计 没头绪...求解答

问题描述 基于java的汽车整车仓储信息系统 怎么写啊毕业设计 没头绪...求解答 我应该找什么资料~~╮(╯▽╰)╭..................................... 解决方案 胡乱抄一点交差,反正你什么都不会,现学现卖都来不及 http://wenku.baidu.com/link?url=Q0PTDWA-QGs0fV5Fhymyjy1uTKDKoatEoboQhWJKLL1yFTIteGba2OVffJdKZcuLNzUKtDnLCxejpRvxLbC0AypZRs