Google App Engine / JavaのGoogle Cloud Endpoints:Objectifyフレームワークでデータベースを操作する

以前の記事( 「JavaのGoogle Cloudエンドポイント:ガイド。パート1 「JavaのGoogle Cloudエンドポイント:ガイド。パート2(フロントエンド) 「JavaのGoogle Cloudエンドポイント:ガイド。パート3」 )では、 Google Cloud EndpointsでのAPIの作成と、 AngularJSでのAPIのフロントエンドを分析しました。



ただし、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:クラウド内の画像とその他のファイル



All Articles