内容


Google App Engine for Java,第 3 部分

持久性和关系

基于 Java 的持久性和 Google App Engine 数据存储

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Google App Engine for Java,第 3 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:Google App Engine for Java,第 3 部分

敬请期待该系列的后续内容。

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 实例,然后,调用 PersistenceManagermakePersistence() 方法。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,那么将出现一条学习曲线,因为存在本文前面描述的抽象泄漏,并且目前的功能要么还无法正常运行,要么还没有进行很好的文档记录。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Open source
ArticleID=428277
ArticleTitle=Google App Engine for Java,第 3 部分: 持久性和关系
publish-date=09142009