Subscribed unsubscribe Subscribe Subscribe

GAE/JのDatastore Low-level APIを使ってみた

ドキュメントを読みつつ、すごく簡単なサンプルを書いてみた。
http://code.google.com/intl/en/appengine/docs/java/javadoc/com/google/appengine/api/datastore/package-summary.html

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
	<servlet>
		<servlet-name>LowLevelAPI</servlet-name>
		<servlet-class>hoge.fuga.piyo.LowLevelAPIServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>LowLevelAPI</servlet-name>
		<url-pattern>/lowlevelapi</url-pattern>
	</servlet-mapping>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
</web-app>
  • LowLevelAPIServlet.java
package hoge.fuga.piyo;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

@SuppressWarnings("serial")
public class LowLevelAPIServlet extends HttpServlet {
    
    private enum Create {
        hoge,
        fuga,
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String create = req.getParameter("create");
        switch(Create.valueOf(create)) {
        case hoge:
            createHoge();
            break;
        case fuga:
            createFuga(req.getParameter("hoge"));
            break;
        }
        resp.sendRedirect("/");
    }

    private void createHoge() {
        DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
        Entity entity = new Entity("Hoge");
        datastoreService.put(entity);
    }
    
    private void createFuga(String hogeId) {
        DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
        Key hogeKey = KeyFactory.createKey("Hoge", Long.parseLong(hogeId));
        // hogeKeyの子としてエンティティを生成
        Entity fuga = new Entity("Fuga", hogeKey);
        datastoreService.put(fuga);
    }
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="com.google.appengine.api.datastore.Query" %>
<%@ page import="com.google.appengine.api.datastore.DatastoreService" %>
<%@ page import="com.google.appengine.api.datastore.DatastoreServiceFactory" %>
<%@ page import="com.google.appengine.api.datastore.Entity" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
	DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
%>
	<dl>
<%
	Query hogeQuery = new Query("Hoge");
	Iterable<Entity> hogeIter = datastoreService.prepare(hogeQuery).asIterable();
	for (Entity hoge : hogeIter) {
%>
		<dt><%= hoge.getKey() %></dt>
<%
		Query fugaQuery = new Query("Fuga", hoge.getKey());
		Iterable<Entity> fugaIter = datastoreService.prepare(fugaQuery).asIterable();
		for (Entity fuga : fugaIter) {
%>
			<dd><%= fuga.getKey() %></dd>
<%
		}
    }
%>
	</dl>
	<form action="/lowlevelapi" method="post">
		<input type="hidden" name="create" value="hoge">
		<input type="submit" value="hoge">
	</form>
<%
	if (hogeIter.iterator().hasNext()) {
%>
	<form action="/lowlevelapi" method="post">
		<input type="hidden" name="create" value="fuga">
		<select name="hoge">
<%
		for (Entity hoge : hogeIter) {
%>
			<option value="<%= hoge.getKey().getId() %>"><%= hoge.getKey() %></option>
<%
		}
%>
		</select>
		<input type="submit" value="fuga">
	</form>
<%
	}
%>
</body>
</html>


実行して動かしてみると、こんなカンジ。


"hoge"ボタンを押すと、"Hoge"というkindのEntityが生成される。
これは普通にEntityのコンストラクタにkind名を渡して、DatastoreServiceからputするだけ。


"fuga"ボタンは、セレクトボックスで選択したHogeエンティティの子としてFugaエンティティを生成する。

Entity(java.lang.String kind, Key parent)
Create a new Entity with the specified kind and parent Entity.

とあるように、Entityクラスのコンストラクタの第1引数にkind名、第2引数にKeyを与えてやると、Keyを親とする子エンティティとしてEntityインスタンスが作られるらしい。
Python版でparentを指定してmodelインスタンスを作るときのイメージと似ていて、分かりやすい。


で、次にQuery。JSPで書いてみたらごちゃごちゃして読みにくくなったけど。。。
普通のQueryは、コンストラクタにkind名を渡してインスタンスを生成。そいつをDatastoreServiceのprepareメソッドに渡してやるとPreparedQueryが返ってくるので、そこからasIterable()なりasIterator()なりをもらってEntityをなめていく、ということになるようだ。

	DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
	Query hogeQuery = new Query("Hoge");
	Iterable<Entity> hogeIter = datastoreService.prepare(hogeQuery).asIterable();
	for (Entity hoge : hogeIter) {
		...
	}

というカンジで。


JDOのQueryと違って、datastoreのQueryはancestorを指定したQueryを生成できる。

Query(java.lang.String kind, Key ancestor)
Create a new Query that finds Entity objects with the specified kind.

GAE/J でancestorによるフィルタリングはできない? - すぎゃーんメモでつまづいた問題も、このLow-level APIを使えば解決できる。

	Query fugaQuery = new Query("Fuga", hoge.getKey());
	Iterable<Entity> fugaIter = datastoreService.prepare(fugaQuery).asIterable();
	for (Entity fuga : fugaIter) {
		...
	}

こうすることで、hogeから得たKeyを親として持つFugaのエンティティを取得することができた。

感想など

Python版に近い感覚でDatastoreを扱うことができるような気がする。
propertyのget/setが大変そう。基本的に

setProperty(java.lang.String propertyName, java.lang.Object value)

でセットして、

getProperty(java.lang.String propertyName)

でゲットすることになるらしい。
DataTypeTranslatorってのを使うと型変換とかをうまくやってくれるんだろうか?まだ使ってみていないので分からない。
あと、上記のサンプルを試しに本番デプロイしてみたところ、DashboardのData Viewerから見ることができなかった。Query書いてみてもServer Errorになってしまったし。。Low-level APIで保存したデータはData Viewerから見られないのかな?それとも何かやり方を間違っているのだろうか?
追記。時間経ってから確認したらちゃんと見られるようになっていた。DataViewerに反映されるまで少し時間がかかるだけかも。