Hatena::ブログ(Diary)

魔法の☆mixiアプリ開発日記 このページをアンテナに追加 RSSフィード

こちらで公開しながらmixiアプリ開発してるんで、物好きな人は覗いてみてください^^
(開発中アプリなんで、利用には承認が必要です)

2010-06-19

Slim3でのデータの取扱い

後でプロジェクトページに書く内容をメモ

http://code.google.com/p/mlmchuchu/


(追記)

あ、なんかコード中の一部が化けてる(汗

はてな記法やwiki構文を無効化にするタグがあるんだろうけど・・・

ちょっと見てみるかな。

とりあえずGoogleCodeの方に反映させました。そっちは大丈夫。


(追記2)

「入力したコードやはてな記法をそのまま表示する(スーパーpre記法)」に書き換え。

http://hatenadiary.g.hatena.ne.jp/keyword/%E5%85%A5%E5%8A%9B%E3%81%97%E3%81%9F%E3%82%B3%E3%83%BC%E3%83%89%E3%82%84%E3%81%AF%E3%81%A6%E3%81%AA%E8%A8%98%E6%B3%95%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%EF%BC%88%E3%82%B9%E3%83%BC%E3%83%91%E3%83%BCpre%E8%A8%98%E6%B3%95%EF%BC%89

これでこっちも大丈夫になったはず。

////////////////////////////////////////////////


本ページでは、Slim3におけるデータの取扱いについて記述しています。

目次は以下のとおりです。


・準備運動

・データの持たせ方を考える

・ユーザIDを元にしてデータの読み書きを行う

・グローバルトランザクションを使う

・楽観的ロックの失敗時にリトライする

・実際の処理を想定したコードを書いてみる

・(追記)運用を見据えた設計

・(追記)Blobで保存してみる



【【【準備運動】】】


まずはSlim3のスタートガイドを一通りやりましょう。

必要なソフトの入手法や、設定方法、モデルの作り方、データの基本的な保存と取得についてはこちらに書かれています。

http://sites.google.com/site/slim3appengine/getting-started

http://sites.google.com/site/slim3documentja/getting-started


それを踏まえたうえで、実際のアプリで必要な処理をいろいろ解説していきたいと思います。



では、まず最初に単純な例をお見せします。

        //データを保存
        Datastore.put(newCharacter);
        
        //データを取得
        ChuchuCharacter chuchuCharacter = Datastore.getOrNull(ChuchuCharacter.class, key);

一応は、これだけでデータの保存と取得を行うことができます。

(キーの生成方法やモデルの作成方法については後述します)


実際に稼働するアプリを創る際には、トランザクションリトライのためのコードが必要になるので、

ほとんどの場合はもっと複雑なコードになりますが、

テストケースの記述等、これだけで十分な場合も多々あります。



ではさっそく、実際に動作するコードとして以下のことを検証してみましょう。

・キャラクター情報を作成し、データを書き込んでみる

・書き込んだデータを取得してみる


手順はこんな感じ。

【モデルとサービスを作成】

f:id:aki-rs:20100619234055j:image

f:id:aki-rs:20100619234056j:image

f:id:aki-rs:20100619234057j:image

f:id:aki-rs:20100619234058j:image

f:id:aki-rs:20100619234059j:image

ここで単純に「Character」という名前のモデルにしてしまうと、

java.lang.characterと名前がかぶって面倒なことになるので、ここでは必ず「ChuchuCharacter」にすること!


テストケースを記述】

f:id:aki-rs:20100619234060j:image

通常はChuchuServiceにコードを記述して、それをテストケースから呼び出すことになりますが、

今回のようにちょっとした検証を行いたい場合など、テストケースに直接書いて済ませるという手もあります。


JUnit実行】

f:id:aki-rs:20100619234061j:image

実行してグリーンになる(場合によってはレッドになる)ことが確認できればOK。


コードは以下の通りとなります。

//【モデル】
package slim3.model;

import java.io.Serializable;

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

import org.slim3.datastore.Attribute;
import org.slim3.datastore.Model;

@Model(schemaVersion = 1)
public class ChuchuCharacter implements Serializable {

    private static final long serialVersionUID = 1L;

    @Attribute(primaryKey = true)
    private Key key;

    @Attribute(version = true)
    private Long version;

    /**
     * 名前
     */
    private String name;
    
    /**
     * 経験値
     */
    private int exp;
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getExp() {
        return exp;
    }

    public void setExp(int exp) {
        this.exp = exp;
    }
    
    /**
     * Returns the key.
     *
     * @return the key
     */
    public Key getKey() {
        return key;
    }

    /**
     * Sets the key.
     *
     * @param key
     *            the key
     */
    public void setKey(Key key) {
        this.key = key;
    }

    /**
     * Returns the version.
     *
     * @return the version
     */
    public Long getVersion() {
        return version;
    }

    /**
     * Sets the version.
     *
     * @param version
     *            the version
     */
    public void setVersion(Long version) {
        this.version = version;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((key == null) ? 0 : key.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        ChuchuCharacter other = (ChuchuCharacter) obj;
        if (key == null) {
            if (other.key != null) {
                return false;
            }
        } else if (!key.equals(other.key)) {
            return false;
        }
        return true;
    }
}
//【サービス】
package slim3.service;

public class SampleService {

}
//【サービスのテストケース】
package slim3.service;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;

import org.junit.Test;
import org.slim3.datastore.Datastore;
import org.slim3.tester.AppEngineTestCase;

import slim3.model.ChuchuCharacter;

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

public class SampleServiceTest extends AppEngineTestCase {

    private SampleService service = new SampleService();

    @Test
    public void test() throws Exception {
        //キーの元となる値
        //実際は、Controllerで取得した値を引数として渡すようにすることが多い
        String opensocialId = "testid";

        //モデルクラスと、キーの元となる値を引数にして、キーを生成する
        Key key = Datastore.createKey(ChuchuCharacter.class, opensocialId);

        //とりあえず取得してみる
        //まだデータを作成していないので、nullになる
        ChuchuCharacter nullCharacter = Datastore.getOrNull(ChuchuCharacter.class, key);
        assertThat(nullCharacter, is(nullValue()));

        //ちなみにDatastore.get#getメソッドもある
        //こちらはデータが存在しない場合にEntityNotFoundRuntimeExceptionを投げてくる
        //ChuchuCharacter nullCharacter2 = Datastore.get(ChuchuCharacter.class, key);

        //登録するキャラクター情報を作成
        //ここでキーをセットしなかった場合、put時に自動でキーが設定される
        //例えば、メッセージや掲示板への書き込み等は自動採番にするのが良い
        ChuchuCharacter newCharacter = new ChuchuCharacter();
        newCharacter.setKey(key);
        newCharacter.setName("チューチューレッド");
        newCharacter.setExp(300);
        
        //保存する
        Datastore.put(newCharacter);
        
        //保存した値を取得し、値を検証する
        ChuchuCharacter chuchuCharacter = Datastore.getOrNull(ChuchuCharacter.class, key);
        assertThat(chuchuCharacter, is(notNullValue()));
        assertThat(chuchuCharacter.getName(), is("チューチューレッド"));
        assertThat(chuchuCharacter.getExp(), is(300));
    }
}

今回の場合、JUnitを実行してみて、バーがグリーンになればOKです。

f:id:aki-rs:20100619234817j:image

無事、保存と取得ができましたね。


ちなみにデータが存在しなければ初回のputで保存され、

存在していれば更新(上書き)となります。

うっかり同一キーを発行してしまった場合などは古いデータは上書きされますので注意しましょう。



とりあえず、これで準備運動は終了です。

それでは次回、【データの持たせ方を考える】に入っていきましょう。

みれいみれい 2010/06/22 09:22 サンプルコードを試させて頂きましたがエラーとなり
実行できませんでした。もう少し調査してみます。

aki-rsaki-rs 2010/06/22 10:19 あらら。どんなエラーが出ます?
こっちで動くコードからコピペしてるので、おそらく環境設定周りの問題かなと思います。

みれいみれい 2010/06/22 14:12 18行目 private SampleService service = new SampleService();

上記部分でserviceがローカルで読み取られないとなります。
で、それ無視して実行すると、

java.lang.IllegalArgumentException: The meta data of the model(slim3.model.ChuchuCharacter) is not found.
at org.slim3.datastore.DatastoreUtil.createModelMeta(DatastoreUtil.java:1468)
at org.slim3.datastore.DatastoreUtil.getModelMeta(DatastoreUtil.java:1395)
at org.slim3.datastore.Datastore.getModelMeta(Datastore.java:2699)
at org.slim3.datastore.Datastore.createKey(Datastore.java:449)
at slim3.service.SampleServiceTest.test(SampleServiceTest.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:592)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

とトレースバックが出力されます。

aki-rsaki-rs 2010/06/23 11:22 警告は
「フィールドが定義されているけど使ってないじゃん!
 保守性下がるからちゃんと消してよね。」
というものですね(^^;
このserviceは後々使うのでここでは残してます。この警告は無視してOKです。

あと、エラーはこちらでも再現できました。
次のエントリに解決法を書きましたので、そっちを参照してください!
http://d.hatena.ne.jp/aki-rs/20100623/1277259439

みれいみれい 2010/06/25 08:08 ファクトリーパスを失念してました。
設定する事でグリーンになる事を確認しました。
ありがとうございます。次の記事を楽しみにしてます。

aki-rsaki-rs 2010/06/25 15:14 無事グリーンになったようでなによりです!
引き続きお楽しみくださ〜い!

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/aki-rs/20100619/1276958710