坚持与关系

本文探讨了AppEngineforJava在实现可伸缩Web应用程序持久层方面的局限性,包括与JDO和JPA集成的问题,关系处理的复杂性,以及去规范化数据的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

App Engine for Java试图消除为可伸缩Web应用程序编写持久层的烦恼,但是实现该目标的效果如何? 在本文中,我以基于Java数据对象(JDO)和Java持久性API(JPA)的持久性框架概述为基础,对Java App Engine进行了总结。 尽管最初有希望,但App Engine基于Java的持久性目前存在一些严重的缺点,我将对此进行说明和演示。 您将了解App Engine for Java持久性的工作原理,面临的挑战以及与Google为Java开发人员使用的云平台一起使用时所具有的持久性选项。

在阅读本文并浏览示例时,您将牢记App Engine for Java当前是预览版这一事实。 尽管基于Java的持久性可能不是您目前希望或需要的全部,但将来可能会并且应该改变。 我在本文的写作中了解到,如今,使用App Engine for Java进行可伸缩的,数据密集型Java应用程序开发并不是胆怯或保守的做法。 这更像是潜入游泳池的深处:没有救生员在眼前,项目是否沉没取决于您。

请注意,本文中的示例应用程序基于本文第2部分中开发的联系人管理应用程序。 您需要构建并运行该应用程序才能继续此处的示例。

细节和泄漏抽象

与原始的Google App Engine一样,Java App Engine依靠Google内部基础架构来进行可扩展应用程序开发的三大要素:分发,复制和负载平衡。 由于您使用的是Google基础架构,因此大多数魔术发生在幕后,您可以通过Java的基于API的API的App引擎进行访问。 数据存储区接口基于JDO和JPA,它们本身都基于开源DataNucleus项目。 AppEngine for Java还提供了一个低级适配器API,用于直接处理App Engine for Java数据存储,该API基于Google的BigTable实现(有关BigTable的更多信息,请参阅第1部分 )。

但是,用于Java数据的App Engine持久性并不像纯Google App Engine中的持久性那么简单。 由于BigTable不是关系数据库,因此JDO和JPA接口提供了一些泄漏的抽象。 例如,在Java的App Engine中,您不能执行连接的查询。 您可以在JPA和JDO中设置关系,但是它们只能用于持久关系。 并且当您持久化对象时,如果它们位于同一实体组中,则只能将它们持久化在同一原子事务中。 按照约定,所拥有的关系与父级在同一实体组中。 相反,未拥有的关系位于单独的实体组中。

重新思考数据标准化

使用App Engine的可伸缩数据存储区时,您需要重新考虑对规范化数据的好处的灌输。 当然,如果您在现实世界中工作了足够长的时间,则可能已经牺牲了一两次性能规范。 区别在于,在处理App Engine数据存储区时,您必须及早且经常取消规范化。 去规范化不再是一个脏话。 相反,它是一种设计工具,将在Java的App Engine的许多方面应用。

当您尝试将为RDBMS编写的应用程序移植到App Engine for Java时,会出现App Engine for Java泄漏持久性的主要缺点。 App Engine for Java数据存储区不能替代关系数据库,因此您对App Engine for Java所做的操作可能不容易转换为RDBMS端口。 采用现有模式并将其移植到数据存储区的可能性很小。 如果您确实决定将旧版Java企业应用程序移植到App Engine,建议您谨慎行事,并进行分析以进行备份。 Google App Engine是专门为其设计应用程序的平台。 Google App Engine for Java对JDO和JPA的支持使这些应用程序可以移植回更传统的,尽管非标准化的企业应用程序。

关系的麻烦

App Engine for Java的当前预览版本的另一个缺点是对关系的处理。 为了创建关系,您当前必须使用特定于App Engine for Java到JDO的扩展。 鉴于键是基于BigTable的工件生成的,也就是说,“主键”的父对象键已编码到其所有子键中,因此您将不得不在非关系数据库中管理数据。 另一个限制是持久化数据。 如果您使用非标准的AppEngine for Java Key类,则会产生复杂性。 首先,在将模型移植到RDBMS时如何使用非标准Key ? 其次,GWT引擎无法转换Key类,因此任何使用此类的模型对象都不能用作GWT应用程序的一部分。

当然,在撰写本文时,Google App Engine for Java处于预览模式。 它不适合黄金时间使用。 在研究JDO中的关系文档时,这一点变得非常清楚,因为JDO稀疏且包含不完整的示例。

适用于Java的App Engine开发套件附带了一系列示例程序。 这些示例中很多都使用JDO,没有一个使用JPA。 没有一个示例(包括一个称为jdoexamples的示例)有一个甚至简单关系的单个示例。 相反,所有示例仅使用一个对象将数据存储在数据存储中。 Google App Engine for Java讨论组充满了有关如何使简单关系正常工作的问题,而答案却很少。 一些开发人员显然已经能够使其工作,但并非没有肘部润滑脂和一些并发症。

App Engine for Java中关系的底线是,您需要在没有JDO或JPA太多支持的情况下对其进行管理。 Google的BigTable是一种成熟的技术,可用于生产可扩展的应用程序,您可以在此基础上进行构建。 在BigTable之上进行构建使您无需处理未完全烘焙的API门面。 另一方面,您将使用较低级别的API。

AppEngine for Java中的Java数据对象

尽管将传统的Java应用程序移植到App Engine for Java中可能没有意义,甚至在遇到关系挑战的情况下,仍然存在一些持久性场景,在这些场景中使用该平台可能是有意义的。 我将以一个有效的示例结束本文,该示例应使您了解App Engine for Java持久性的工作方式。 我们将从第2部分中构建的联系人管理应用程序开始,这一次将逐步介绍使用Java数据存储设施通过App Engine添加对持久化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. ContactDAOMock模拟DAO
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插件编写的应用程序已经具有使用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 ,然后调用makePersistence()的方法PersistenceManagermakePersistence()方法采用临时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复制属性。 这些属性作为参数传递给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.联系人是持久的
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 ContactJdoDAO 。 然后,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当前对持久性的支持,这是可扩展应用程序交付的基础之一。 尽管要牢记这是一个不断发展的平台,但重要的是总体发现令人失望。 尽管为Java预览版App Engine编写的应用程序可能使用JDO或JPA,但它们仍与App Engine的持久性基础结构绑定在一起。 App Engine for Java的预览版也几乎没有提供有关其持久性框架的文档,而App Engine for Java附带的示例几乎使简单的关系无法工作。

即使JDO和JPA实现完全成熟,当前也不太可能编写Java应用程序的App Engine并将其轻松移植到基于RDBMS的企业应用程序。 至少,您必须进行一些繁琐的编码才能使端口正常工作。

我希望持久性会随着时间的推移而成熟。 如果您确实需要立即使用App Engine for Java,则可能需要绕过Java API并直接写入低级数据存储区API。 可以与App Engine for Java平台一起使用,但是如果您习惯于使用JPA和/或JDO,则由于本文开头描述的泄漏抽象以及两者都没有的功能,您将遇到学习上的困难。尚不能很好地工作,或者目前没有很好的记录。


翻译自: https://www.ibm.com/developerworks/java/library/j-gaej3/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值