Google Cloud Platform: 在 Google Cloud Datastore 中存储数据





0/5 (0投票)
Google Cloud Platform - 第 6 部分:
- Google Cloud Platform:Google App Engine 入门(第一部分)
- Google Cloud Platform:使用 Google App Engine 进行部署(第二部分)
- Google Cloud Platform:用户管理(第三部分)
- Google Cloud Platform:移动端点(第四部分)
- Google Cloud Platform - Google Cloud SQL(第五部分)
- Google Cloud Platform - Google Cloud Datastore(第六部分)
- Google Cloud Platform - Google Cloud Storage(第七部分)
第 6 部分:Google Cloud Datastore
欢迎回来,这是我们关于 Google Cloud Platform 系列的第六篇文章。如果您属于那种害怕错过(FOMO)的人,那么您最好从这里的第一部分开始阅读;但如果您不介意稍微迟到,请继续阅读——我们正在使用 Google Cloud Platform 构建一个应用程序,在本期中,我们将继续研究大多数应用程序都需要执行的一项任务:存储数据。
正如我们上次提到的,Google 提供了几种不同的数据存储方式:Google Cloud SQL,我们上次已经介绍过,适用于希望以传统关系数据库和关系模型方式存储数据的应用程序。还有Google Cloud Storage客户端,它更适合“大型二进制文件”,例如图片和视频,我们将在下一期中进行介绍。最后,还有Google Cloud Datastore,这是一种非关系型“NoSQL”数据存储方法,我们将在稍后在此处进行介绍。
然而,在我们这样做之前,有一点非常重要需要重复:尽管各种技术评论家和倡导者可能不同意,但这些方法中没有一种比其他方法“更好”。那些宁愿盲目遵循行业评论家所说的“最佳实践”的人会讨厌听到我说这些,但事实是,每一种方法都解决了不同类型的问题,有时最佳方法是同时使用所有方法,这种技术有时被称为“多态持久化”或“多存储持久化”。或者,正如一位著名作家曾经说过的:“从每个数据库,根据其能力,到每个项目,根据其需求。”
(好吧,我承认,那位作家就是我,刚才说的。但它听起来不错,不是吗?)
Google Cloud Datastore
Google Cloud SQL的优势在于它构建在用户熟悉的 JDBC 编程模型之上,Java 开发人员可以轻松地编写代码。另一方面,Google Cloud Datastore API 并不是许多 Java 开发人员所熟悉的;幸运的是,它们提供了两种(和三种 API)的访问方式:一种基于 JDO 或 JPA 的方法,鼓励开发人员构建持久化类,并将持久化的细节留给库处理;另一种是“低级”API,旨在提供对Google Cloud Datastore存储层的原始细节的访问。(对于那些真正好奇的人,Google Cloud Datastore构建在 Google 的“Bigtable”存储系统之上,这是最早的 NoSQL 存储系统之一,其详细信息可在http://static.googleusercontent.com/media/research.google.com/en/us/archive/bigtable-osdi06.pdf上找到,但读者请注意,这是一篇完整的学术论文,毫不含糊。)
根据您偏好的方法,使用 Google Cloud Datastore 的细节会有所不同。JDO 和 JPA 方法都需要开发人员编写“持久化类”,这些类需要符合特定的限制,并在构建过程中进行“增强”(修改),以添加额外功能,使持久化(在很大程度上)对应用程序开发人员透明。对于不希望(或不需要)关心低级细节的开发人员来说,这通常是更好的方法。
然而,高级 API 的缺点在于它们是高级 API,有时“原始”低级方法会提供某些优势。所以,同样,没有一种方法比另一种“更好”,尽管可以说,对于持久化一个给定类的实例,高级 API 所需的代码行数比低级 API 少,而低级 API 会提供更精细级别的控制。
无论采用哪种方法,Google Cloud Datastore都有几个优点和缺点。首先,Google Cloud Datastore的“实体”不是以表格关系格式存储的;其格式具有一定的表格形状,允许实体将数据元素存储为其直接依赖的数据(“属性”),但它不将“关系”(外键关系)视为模型的核心部分,也不支持像 SQL 提供的即席查询格式。作为回报,它会自动分发数据以管理非常大的数据集,并支持极其快速的查询,这很大程度上是因为查询是提前已知的,并且可以在查询实际运行很久之前进行优化。这是一种权衡,易于访问与规模,但多存储方法的美妙之处在于,高度关系型数据可以存储在Google Cloud SQL中,大规模数据可以存储在Google Cloud Datastore中,并且都可以从同一个应用程序访问和使用。
存储在Google Cloud Datastore中的实体访问方式与我们习惯的基于 SQL 的数据库不同——实体以层次结构(类似于文件系统中的文件存储方式)进行组织,因此除了系统中的“根”实体外,它们都有一个父实体。查找实体,就是导航到给定实体的子路径的过程,就像在文件系统中查找文件是导航通过目录到目标文件一样。
在代码中更容易看到,所以让我们并排看看两种高级方法。如果您觉得这两种方法都不太符合您的口味,Google 还建议了另外三个开源框架,它们构建在 Google Cloud Datastore API 之上:Objectify(https://code.google.com/p/objectify-appengine/)、Twig(https://code.google.com/p/twig-persist/)和 Slim3(https://sites.google.com/site/slim3appengine/)。更多细节可以在它们各自的首页上找到。
JDO
Java Data Objects 是 JPA 在 JavaEE 世界的“ORM 之战”中的前身,其语法在外观和感觉上与 90 年代末面向对象世界中的面向对象数据库非常相似。(Versant,特别是,似乎对 JDO 规范产生了很大的影响——至少,根据我使用 Versant 然后使用 JDO 的经验。)
JDO 的包是 `javax.jdo`,它使用注解来装饰 Java 类,以描述存储在Google Cloud Datastore中的实体和实体的属性。它需要一个“增强”步骤(回想一下,在本系列第一部分中,我们不得不禁用这个增强步骤,所以这实际上意味着重新启用 Ant 构建脚本的那个步骤,或者如果您使用的是来自Google App Engine SDK的全新项目模板,则什么都不做),该步骤会生成这些类的修改版本,并将存储功能巧妙地集成进去。
因为 JDO 的知名度不如 JPA,而且 JPA 经常与关系数据库相关联(这有时会在新用户的脑海中产生一些错误的等同),所以我们将使用它作为代码示例。请注意,尤其是在最基本的使用级别上,JDO 和 JPA 几乎可以互换使用,因此更熟悉 JPA 的读者可以自由地使用 JPA 代替。
JPA
Java Persistence API 是 JavaEE 堆栈中管理对象/关系阻抗不匹配的官方授权 API,它在很大程度上受到了 Hibernate 开源项目的成功影响。(在某些方面,如果“ORM 之战”可以分出胜负的话,它也可以称为“赢家”。)JPA 注解定义在 `javax.jpa` 包之外,与 JDO 一样,开发人员会用 JPA 注解来装饰需要持久化的类,以描述要存储在Google Cloud Datastore中的实体。
低级
顾名思义(因为它本质上运行在 Google 的 BigTable 系统之上),也可以从更低的级别来查看/访问一个给定的数据存储。虽然能够“看透”通过 JDO 或 JPA 存储的对象(到存储系统中)可能很有帮助,例如 Google Cloud Datastore API 提供的内置数据类型(电话号码、电子邮件、无限长度文本字段、URL 链接等),但 Java 开发人员在大多数情况下不需要使用低级 API,在这里提到它主要是为了完整性。
代码
概念上的分解够了,我们来看一些代码。
到目前为止,应用程序一直在向访问网站(或者,如上一期所示,移动端点)的用户打招呼,但没有历史记录。市场部已决定应用程序需要跟踪用户访问我们的情况,并且那些以前来过的人会得到更个性化和/或更真诚的问候。这意味着,从实际操作上看,我们希望跟踪给定用户(通过移动端点参数给出)访问端点的时间/日期,以及我们这次发送给他们的消息(以避免明显的重复)。
首先,让我们重新定义 `Message` 类,使其具有持久性,并包含问候的时间戳和问候的目标。我们仍然允许 `Message` 从类外部传入,以提供最大的灵活性来决定消息内容。(开发者审美可能不同——如果您更喜欢让 `Message` 封装实际的消息选择,那是一个完全合理的决定。我个人更喜欢我的数据存储类型是相当“笨拙”的数据传输对象。)
从 JDO 的角度来看,这意味着该类需要在类级别用 JDO `@PersistentCapable` 注解进行注解,表明该类需要被增强;而要存储的字段则需要用 JDO `@Persistent` 注解进行注解。有些情况下 `@Persistent` 是不是必需的,但即使它是冗余的,包含它也没有坏处。JDO 还要求在类上定义一个字段来存储持久化对象的**主键**,所以我们添加了一个。
@PersistenceCapable class Message { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent public String target; @Persistent public String message; @Persistent public Date timestamp; public Message(String t, String m) { target = t; message = m; timestamp = new Date(); } public String getMessage() { return message; } public void setMessage(String value) { message = value; } public Date getTimestamp() { return timestamp; } public String getTarget() { return target; } }
JDO 还支持“可序列化类型”的概念,这意味着任何被标记为 `Serializable`(通过实现标记接口)的类都将被序列化为“blob”——一个直接的字节数组二进制值——用于那些实体想要存储某些依赖数据但不真正需要查询或索引该数据的场景。例如,如果我们想在 `Message` 中存储图片,这很容易做到,作为 `Message` 内部的一个实现 `Serializable` 的字段类型,并且不需要进一步的操作来启用它,假设实际存储的 Image 或其他类是 `Serializable` 的。(请注意,由于集合是 `Serializable` 的,一个实体可以存储一个集合作为字段,并且集合中的项目——假设它们也都是 `Serializable` 的——将与实体本身一起存储。但是,集合中的项目都将作为二进制 blob 存储,这意味着它们将无法作为查询谓词参数来访问。)
从开发者的角度来看,这就是使 `Message` 对象持久化的所有必要步骤——完成上述更改后,我们可以执行“ant enhance”,它依赖于“compile”任务,然后 Ant 会将代码通过 Java 编译器,然后通过 DataNucleus(用于 JDO 和 JPA 持久化的工具)增强器,并将代码存入项目结构中“src”目录旁边的生成的“war”目录中。
然而,从Google App Engine的构建链的角度来看,还需要一个必要的更改,那就是“JDO 配置文件”(`jdoconfig.xml`),它必须位于一个非常特定的位置:`war/WEB-INF/classes/META-INF` 目录。(本质上,JDO 配置文件必须出现在它所描述的类所处的“META-INF”目录中,因此,由于这些类是 servlet WAR 格式的一部分,它位于 `WEB-INF/classes` 子目录中。)默认的项目模板在 `src/META-INF` 目录中有一个版本,看起来是这样的:
<?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.api.jdo.JDOPersistenceManagerFactory"/>
<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"/>
<property name="datanucleus.appengine.singletonPMFForName"
value="true"/>
</persistence-manager-factory>
</jdoconfig>
与所有 XML 文件一样,请确保所有非引号字符串的拼写和大小写与上述定义完全一致;通常,默认的 `jdoconfig.xml` 文件就可以,最好就从它开始,直到出现需要更改的情况。
下一步是当我们想找到所有 `target` 字段的值为特定目标的所有 `Message`,以决定返回哪个 `Message`,以及存储我们创建并返回的 `Message`。这两个步骤都需要使用 JDO `PersistenceManager`,它通过 `JDOHelper` 静态类获取 `PersistenceManagerFactory`,然后 `PersistenceManagerFactory` 提供一个 `PersistenceManager` 实例。
public class Greetings { private static final PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory("transactions-optional"); public Message greet(@Named("target") String target) { PersistenceManager pm = pmf.getPersistenceManager(); try { Message msg = new Message( target, "Hello, " + target + ", from Google Cloud Endpoints!"); pm.makePersistent(msg); return msg; } finally { pm.close(); } } }
请注意,用于获取 `PersistenceManagerFactory` 的字符串必须与 `jdoconfig.xml` 文件中列出的匹配;这是为了允许开发人员使用不同类型的 `PersistenceManager`(一种要求事务,一种允许事务可选,等等)。一旦我们有了 `PersistenceManager`,使用 `makePersistent()` 方法调用来进行实际存储就会变得很简单;JDO 和 Google Cloud Datastore API 会完成剩下的工作。
摘要
如前所述,JDO 并不是访问Google Cloud Datastore的唯一方法;JPA 标准同样受到支持,并且对于某些开发人员来说,如果他们从使用 Hibernate 或更新的 JavaEE 标准技术中熟悉它,这可能是开始使用Google Cloud Datastore的一个更容易的切入点。而且,正如人们很容易推测的那样,JDO 的内容远不止我们在这里看到的——DataNucleus 项目提供了更多关于 JDO 的文档,包括一些关于如何在各种不同场景中使用它的精彩示例;任何想用 JDO 做非琐碎事情的人都应该花一些高质量的时间在那里。(这是 Google 使用成熟的 Java API 标准(如 JDO 和 JPA)的一个好处——有大量现成的文档,所以我们可以利用它们来学习和使用这些工具。)
但与此同时,我们现在有了一个关于我们问候过的人的记录,我们可以利用这些数据来改变消息——对于那些以前从未到过我们这里的人,给他们一个非常礼貌的问候(“很高兴认识您”);而对于那些来过很多次的人,则给他们一个更随意友好的问候(“WHAZZZZUPPPPP?!?”)。未来的定制是无穷无尽的,这很好,因为这显然是互联网的下一个大事件。
在下一篇文章中,我们将讨论如何通过在问候中包含视频或图片来进一步增强问候,但现在,祝您编码愉快!