ただし、APIの作成に関するガイダンスは、データベースを操作しないと不完全です。
この記事では、 GA EngineのApp Engine Datastoreデータベースを操作するためのObjectifyフレームワークについて説明します 。
App Engineデータストア
App Engineデータストアは、Key-Valueデータベースタイプの非リレーショナルNoSQLデータベース(スキーマレスNoSQLデータストア)です。
キー
キーは、データベース内の「オブジェクト」(App Engineデータストアでは「エンティティ」と呼ばれる)の一意の識別子です。
キーは3つのコンポーネントで構成されています。
種類 (タイプ):データベース内のオブジェクトのタイプに対応します(Objectifyを使用してkindをJavaクラスとしてモデル化します。つまり、 相対的に言えば、この場合、kindはデータベース内にあるオブジェクトのクラスを意味します)
識別子 :オブジェクトの一意の識別子。文字列(String)の場合はnameと呼ばれ、番号(Long)の場合はIdと呼ばれます。 つまり タイプ
"01234"
の
01234
はnameで 、タイプ
01234
の
01234
はIdです。 識別子は、同じタイプのオブジェクト間で一意である必要があります。異なるタイプのオブジェクトは同じ識別子を持つことができます。 識別子「01」を持つタイプ「row」のオブジェクトと、識別子「01」を持つタイプ「column」のオブジェクトを持つことができます。 データベースに新しく作成されたオブジェクトについては、明示的に指定されていない場合、識別子が自動的に生成されます。
親 (オブジェクトのグループ):データベース内のオブジェクトを「オブジェクトのグループ」にまとめることができます。このため、「親」オブジェクトのキーを親で指定するか、グループに含まれないオブジェクトをnull(デフォルト)で指定します。
エンティティ
データベース内のオブジェクト(エンティティ)には、値(値型)を含むことができるプロパティがあります。Javaデータ型(Java型)への対応は、表に示されています。
値タイプ | Javaタイプ(s) | ソート順 | 注釈 |
---|---|---|---|
整数 | short
int
long
java.lang.Short
java.lang.Integer
java.lang.Long
| 数値 | |
浮動小数点 | float
double
java.lang.Float
java.lang.Double
| 数値 | 64ビットの倍精度、
IEEE 754 |
ブール | boolean
java.lang.Boolean
| false
または true
| |
テキスト文字列(短い) | java.lang.String
| Unicode | 最大1500バイト
1500バイトを超える値は IllegalArgumentException
スローします |
テキスト文字列(長い) | com.google.appengine.api.datastore.Text
| なし | 最大1メガバイト
索引付けされていません |
バイト文字列(短い) | com.google.appengine.api.datastore.ShortBlob
| バイトオーダー | 最大1500バイト
1500バイトを超える値は IllegalArgumentException
スローします |
バイト文字列(長) | com.google.appengine.api.datastore.Blob
| なし | 最大1メガバイト
索引付けされていません |
日時 | java.util.Date
| 時系列 | |
地理的なポイント | com.google.appengine.api.datastore.GeoPt
| 緯度によって、
次に経度 | |
住所 | com.google.appengine.api.datastore.PostalAddress
| Unicode | |
電話番号 | com.google.appengine.api.datastore.PhoneNumber
| Unicode | |
メールアドレス | com.google.appengine.api.datastore.Email
| Unicode | |
Googleアカウントユーザー | com.google.appengine.api.users.User
| メールアドレス
ユニコード順 | |
インスタントメッセージングハンドル | com.google.appengine.api.datastore.IMHandle
| Unicode | |
リンク | com.google.appengine.api.datastore.Link
| Unicode | |
カテゴリー | com.google.appengine.api.datastore.Category
| Unicode | |
格付け | com.google.appengine.api.datastore.Rating
| 数値 | |
データストアキー | com.google.appengine.api.datastore.Key
または参照されるオブジェクト(子として) | パス要素別
(種類、識別子、 種類、識別子...) | 最大1500バイト
1500バイトを超える値は IllegalArgumentException
スローします |
ブロブストアキー | com.google.appengine.api.blobstore.BlobKey
| バイトオーダー | |
埋め込みエンティティ | com.google.appengine.api.datastore.EmbeddedEntity
| なし | 索引付けされていません |
ヌル | null
| なし |
データベース操作
Objectifyは3つの基本操作を実行します。
save() :データベースにオブジェクトを保存します
delete() :データベースからオブジェクトを削除します
load() :データベースからオブジェクトまたはオブジェクトのリスト(リスト)をロードします。
トランザクションとエンティティグループ
オブジェクトをグループにまとめるために、「親」オブジェクトはデータベースに存在する必要はなく、オブジェクトキーを指定するだけです。 「親」を削除しても、「子」は削除されず、引き続きそのキーを参照します。
このメカニズムを使用すると、データベース内のオブジェクトを階層構造の形式で編成できます。
「親オブジェクト」-「子オブジェクト」(親-子関係)の関係は、同じタイプのオブジェクト(たとえば、great祖父->祖父->父-> I->息子)と異なるタイプのオブジェクト(たとえば、タイプ「car」の子オブジェクトのオブジェクトは、タイプ「wheel」、「engine」のオブジェクトにすることができます)
この場合、各「子」オブジェクトは「親」オブジェクトを1つだけ持つことができます。 また、親オブジェクトのキーはオブジェクトのキーの一部であるため、オブジェクトの作成後に追加または削除することはできません-キーは変更されません。 したがって、「親キー」の使用には注意が必要です。
原則として、単一のトランザクション内では、オブジェクトの1つのグループのみからデータにアクセスできます(ただし、1つのトランザクションで複数のグループを使用する方法があります)
グループ内のオブジェクトがグループに対して変更されると、タイムスタンプが変更されます。 タイムスタンプはグループ全体に設定され、グループ内のオブジェクトが変更されると更新されます。
トランザクションを作成すると、トランザクションの影響を受けるオブジェクトの各グループは、このトランザクションに参加しているとマークされます。 トランザクションがコミットされると、トランザクションに関係するグループのすべてのタイムスタンプがチェックされます。 タイムスタンプのいずれかが変更された場合(その時点で別のトランザクションがグループ内のオブジェクトを変更したため)、トランザクション全体がキャンセルされ、ConcurrentModificationExceptionがスローされます。 詳細については、 github.com / objectify / objectify / wiki / Concepts#optimistic-concurrencyを参照してください。
Objectifyはこの種の例外を処理し、トランザクションを繰り返します。 したがって、トランザクションはべき等でなければなりません。 トランザクションを何回でも繰り返し、同じ結果を得ることができる必要があります。
Objectifyのトランザクションの詳細については、 github.com / objectify / objectify / wiki / Transactionsをご覧ください。
Objectifyをプロジェクトに接続します
フレームワークを使用するには、objectify.jarとguava.jarをプロジェクトに追加する必要があります。
ObjectifyはMavenリポジトリにあり 、pom.xmlに追加するだけです。
<dependencies> <dependency> <groupId>com.googlecode.objectify</groupId> <artifactId>objectify</artifactId> <version>5.1.9</version> </dependency> </dependencies>
-objectify.jarおよびguava.jarがプロジェクトに追加されます。
Objectifyは、WEB-INF / web.xmlで記述する必要があるフィルターを使用します。
<filter> <filter-name>ObjectifyFilter</filter-name> <filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class> </filter> <filter-mapping> <filter-name>ObjectifyFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
データベース内のエンティティ(Entity)をモデル化するUserDataクラスを作成しましょう。
package com.appspot.hello_habrahabr_api; import com.googlecode.objectify.annotation.Entity; import com.googlecode.objectify.annotation.Id; import com.googlecode.objectify.annotation.Index; import com.googlecode.objectify.annotation.Cache; import java.io.Serializable; @Entity // indicates that this is an Entity @Cache // Annotate your entity classes with @Cache to make them cacheable. // The cache is shared by all running instances of your application // and can both improve the speed and reduce the cost of your application. // Memcache requests are free and typically complete in a couple milliseconds. // Datastore requests are metered and typically complete in tens of milliseconds. public class UserData implements Serializable { @Id // indicates that the userId is to be used in the Entity's key // @Id field can be of type Long, long, or String // Entities must have have at least one field annotated with @Id String userId; @Index // this field will be indexed in database private String createdBy; // email @Index private String firstName; @Index private String lastName; private UserData() { } // There must be a no-arg constructor // (or no constructors - Java creates a default no-arg constructor). // The no-arg constructor can have any protection level (private, public, etc). public UserData(String createdBy, String firstName, String lastName) { this.userId = firstName + lastName; this.createdBy = createdBy; this.firstName = firstName; this.lastName = lastName; } /* Getters and setters */ // You need getters and setters to have a serializable class if you need to send it from backend to frontend, // to avoid exception: // java.io.IOException: com.google.appengine.repackaged.org.codehaus.jackson.map.JsonMappingException: No serializer found for class ... // public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getCreatedBy() { return createdBy; } public void setCreatedBy(String createdBy) { this.createdBy = createdBy; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } }
次に、データベース内のオブジェクトを記述するために作成されたクラスを登録するクラスを作成する必要があります。このクラスには、Objectifyサービスオブジェクト(Objectifyサービスオブジェクト)を発行するメソッドが含まれます。 OfyServiceと呼びましょう:
package com.appspot.hello_habrahabr_api; import com.googlecode.objectify.Objectify; import com.googlecode.objectify.ObjectifyFactory; import com.googlecode.objectify.ObjectifyService; /** * Custom Objectify Service that this application should use. */ public class OfyService { // This static block ensure the entity registration. static { factory().register(UserData.class); } // Use this static method for getting the Objectify service factory. public static ObjectifyFactory factory() { return ObjectifyService.factory(); } /** * Use this static method for getting the Objectify service object in order * to make sure the above static block is executed before using Objectify. * * @return Objectify service object. */ @SuppressWarnings("unused") public static Objectify ofy() { return ObjectifyService.ofy(); } }
ここでAPIを作成します(UserDataAPI.javaファイルを呼び出しましょう):
package com.appspot.hello_habrahabr_api; import com.google.api.server.spi.config.Api; import com.google.api.server.spi.config.ApiMethod; import com.google.api.server.spi.config.ApiMethod.HttpMethod; import com.google.api.server.spi.config.Named; import com.google.api.server.spi.response.NotFoundException; import com.google.api.server.spi.response.UnauthorizedException; import com.google.appengine.api.users.User; import com.googlecode.objectify.Key; import com.googlecode.objectify.Objectify; import java.io.Serializable; import java.util.List; import java.util.logging.Logger; /** * explore this API on: * hello-habrahabr-api.appspot.com/_ah/api/explorer * {project ID}.appspot.com/_ah/api/explorer */ @Api( name = "userDataAPI", // The api name must match '[az]+[A-Za-z0-9]*' version = "v1", scopes = {Constants.EMAIL_SCOPE}, clientIds = {Constants.WEB_CLIENT_ID, Constants.API_EXPLORER_CLIENT_ID}, description = "UserData API using OAuth2") public class UserDataAPI { private static final Logger LOG = Logger.getLogger(UserDataAPI.class.getName()); // Primitives and enums are not allowed as return type in @ApiMethod // So we create inner class (which should be a JavaBean) to serve as wrapper for String private class MessageToUser implements Serializable { private String message; public MessageToUser() { } public MessageToUser(String message) { this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } @ApiMethod( name = "createUser", path = "createUser", httpMethod = HttpMethod.POST) @SuppressWarnings("unused") public MessageToUser createUser(final User gUser, @Named("firstName") final String firstName, @Named("lastName") final String lastName // instead of @Named arguments, we could also use // another JavaBean for modelling data received from frontend ) throws UnauthorizedException { if (gUser == null) { LOG.warning("User not logged in"); throw new UnauthorizedException("Authorization required"); } Objectify ofy = OfyService.ofy(); UserData user = new UserData(gUser.getEmail(), firstName, lastName); ofy.save().entity(user).now(); return new MessageToUser("user created: " + firstName + " " + lastName); } @ApiMethod( name = "deleteUser", path = "deleteUser", httpMethod = HttpMethod.DELETE) @SuppressWarnings("unused") public MessageToUser deleteUser(final User gUser, @Named("firstName") final String firstName, @Named("lastName") final String lastName ) throws UnauthorizedException { if (gUser == null) { LOG.warning("User not logged in"); throw new UnauthorizedException("Authorization required"); } Objectify ofy = OfyService.ofy(); String userId = firstName + lastName; Key<UserData> userDataKey = Key.create(UserData.class, userId); ofy.delete().key(userDataKey); return new MessageToUser("User deleted: " + firstName + " " + lastName); } @ApiMethod( name = "findUsersByLastName", path = "findUsersByLastName", httpMethod = HttpMethod.GET) @SuppressWarnings("unused") public List<UserData> findUsers(final User gUser, @Named("query") final String query ) throws UnauthorizedException, NotFoundException { if (gUser == null) { LOG.warning("User not logged in"); throw new UnauthorizedException("Authorization required"); } Objectify ofy = OfyService.ofy(); List<UserData> result = ofy.load().type(UserData.class).filter("lastName ==", query).list(); // for queries see: // https://github.com/objectify/objectify/wiki/Queries#executing-queries if (result.isEmpty()) { throw new NotFoundException("no results found"); } return result; // we need to return a serializable object } }
{project ID} .appspot.com / _ah / api / explorerで、Webインターフェースを使用してデータベースからオブジェクトを追加、削除、ロードすることでAPIをテストできます。
開発者のconsole.developers.google.com/datastore/entities/queryのコンソールで適切なプロジェクトを選択すると、オブジェクトの作成、削除、並べ替えなど、データベースを操作できるウェブインターフェースにアクセスできます。
参照:
Wikiのオブジェクト化
JavaDocのオブジェクト化
Java Datastore API
データストアへのデータの保存 (Googleチュートリアル)
Google I / O 2011の作成者Jeff Schnitzer ( @jeffschnitzer )によるフレームワークの簡単な紹介: youtu.be/imiquTOLl64 ?t=3m40s
UPD:
Javaを使用したGoogle Cloud Storage:クラウド内の画像とその他のファイル