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

GAE/JのDatastore Low-level APIを使ってみた - すぎゃーんメモの続き。
せっかく親子関係の扱いができるのでTransactionを使用してみた。あとEntityを生で使うのがちょっとアレだと思ったので簡単なラップクラスを作ってみた。…けど、微妙。

  • LowLevelAPIServlet.java
package hoge.fuga.piyo;

import hoge.fuga.piyo.model.Fuga;
import hoge.fuga.piyo.model.Hoge;

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.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Transaction;

@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"), req.getRemoteAddr());
            break;
        }
        resp.sendRedirect("/");
    }

    private void createHoge() {
        DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
        Hoge hoge = new Hoge();
        datastoreService.put(hoge.getEntity());
    }
    
    private void createFuga(String hogeId, String remoteAddr) {
        DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
        Key hogeKey = KeyFactory.createKey(Hoge.class.getSimpleName(), Long.parseLong(hogeId));
        
        try {
            Transaction transaction = datastoreService.beginTransaction();
            
            Hoge hoge = new Hoge(datastoreService.get(transaction, hogeKey));
            hoge.setCount(hoge.getCount() + 1);
            // hogeKeyの子としてエンティティを生成
            Fuga fuga = new Fuga(hogeKey);
            fuga.setRemoteAddr(remoteAddr);
            datastoreService.put(transaction, fuga.getEntity());
            datastoreService.put(transaction, hoge.getEntity());
            
            transaction.commit();
        } catch (Exception e) {
        } finally {
            Transaction currentTransaction = datastoreService.getCurrentTransaction(null);
            if (currentTransaction != null) {
                currentTransaction.rollback();
            }
        }
    }
}
package hoge.fuga.piyo.model;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;

public abstract class MyEntity {
    
    final protected Entity entity;
    
    protected MyEntity(Entity entity) {
        this.entity = entity;
    }
    
    protected MyEntity() {
        entity = new Entity(getKind());
    }
    
    protected MyEntity(Key parent) {
        entity = new Entity(getKind(), parent);
    }

    protected MyEntity(String keyName) {
        entity = new Entity(getKind(), keyName);
    }
    
    protected MyEntity(String keyName, Key parent) {
        entity = new Entity(getKind(), keyName, parent);
    }
    
    public Entity getEntity() {
        return entity;
    }
    
    abstract protected String getKind();
    
}
package hoge.fuga.piyo.model;

import com.google.appengine.api.datastore.Entity;

public class Hoge extends MyEntity {

    private static final String COUNT = "count";
    
    public Hoge() {
        super();
        setCount(Long.valueOf(0));
    }
    
    public Hoge(Entity entity) {
        super(entity);
    }

    @Override
    public String getKind() {
        return this.getClass().getSimpleName();
    }

    public void setCount(Long count) {
        entity.setProperty(COUNT, count);
    }

    public Long getCount() {
        return (Long)entity.getProperty(COUNT);
    }
    
}
package hoge.fuga.piyo.model;

import java.util.Date;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;

public class Fuga extends MyEntity {
    
    private static final String REMOTE_ADDR = "remote_addr";
    private static final String DATE_TIME   = "date_time";
    
    public Fuga(Key parent) {
        super(parent);
        setDateTime(new Date());
    }
    
    public Fuga(Entity entity) {
        super(entity);
    }
    
    public Date getDateTime() {
        return (Date)entity.getProperty(DATE_TIME);
    }
    
    public void setDateTime(Date date) {
        entity.setProperty(DATE_TIME, date);
    }
    
    public String getRemoreAddr() {
        return (String)entity.getProperty(REMOTE_ADDR);
    }
    
    public void setRemoteAddr(String remoteAddr) {
        entity.setProperty(REMOTE_ADDR, remoteAddr);
    }

    @Override
    protected String getKind() {
        return this.getClass().getSimpleName();
    }

}
<%@ 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" %>
<%@ page import="hoge.fuga.piyo.model.Hoge" %>
<%@ page import="hoge.fuga.piyo.model.Fuga" %>
<!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.class.getSimpleName());
	Iterable<Entity> hogeIter = datastoreService.prepare(hogeQuery).asIterable();
	for (Entity hoge : hogeIter) {
%>
		<dt>
			<%= hoge.getKind() %><%= hoge.getKey().getId() %>(<%= new Hoge(hoge).getCount() %>)
		</dt>
<%
		Query fugaQuery = new Query(Fuga.class.getSimpleName(), hoge.getKey());
		Iterable<Entity> fugaIter = datastoreService.prepare(fugaQuery).asIterable();
		for (Entity entity : fugaIter) {
		    Fuga fuga = new Fuga(entity);
%>
			<dd><%= fuga.getRemoreAddr() %>(<%= fuga.getDateTime() %>)</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.getKind() %><%= hoge.getKey().getId() %>
			</option>
<%
		}
%>
		</select>
		<input type="submit" value="fuga">
	</form>
<%
	}
%>
</body>
</html>


Transactionの使い方はJDOのものとそんなに大きく変わらないっぽい。

    try {
        DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
        // Transaction開始
        Transaction transaction = datastoreService.beginTransaction();

        // get時、put時に第1引数にTransactionを与える
        Entity entity = datastoreService.get(transaction, key));
        ...
        datastoreService.put(transaction, entity);
        
        // Commit
        transaction.commit();
    } catch (Exception e) {
        // 例外処理
    } finally {
        // Transaction処理の成否チェック
        Transaction currentTransaction = datastoreService.getCurrentTransaction(null);
        if (currentTransaction != null) {
            currentTransaction.rollback();
        }
    }

DatastoreServiceのgetCurrentTransaction(Transaction returnedIfNoTxn)にnullを渡すとまだ処理の完了していないTransactionを得ることができる、とか何とか。
ちなみにローカルの開発サーバーではrollback()が動かなかった。rollbackされない罠。


実行画面は前回とほぼ一緒。ただプロパティをいくつか持たせるようにしてみただけ。
Entityからプロパティのset/getを毎回String propertyNameで指定するのがあまりにもアレなのでラップしてみようと思ってMyEntityクラスを作ってそれを継承したクラスを使うようにしてみたけど、どうも色々イケてない。要検討。