ActiveAndroidを使ってみた

はじめに


久しぶりにオレオレ用のAndroidアプリを書きました。
その際、DBを使ったのですが、面倒くさいことはしたくなかったので
Android向けO/R MapperであるActiveAndroid使いました。

その時のメモを備忘録な感じで残します。

準備


以下のようにして、ライブラリを自分でビルドします。
ビルド完了後、プロジェクトのビルドパスにライブラリを追加します。

1
2
3
$ git clone https://github.com/pardom/ActiveAndroid.git
$ cd ActiveAndroid
$ sh gradlew build

設定


2種類の方法で利用準備ができます。

  1. AndroidManifest.xmlに設定する
  2. Applicationクラスを継承した独自のクラスを用意する

1のやり方は以下のような形で定義します。

  • meta-dataタグを用意し、そこにDB名前とバージョンを記載
  • android:nameに「com.activeandroid.app.Application」を記載
1
2
3
4
5
6
7
8
9
10
11
<application
        android:allowBackup="true"
        android:name="com.activeandroid.app.Application">
        ・・・

        <meta-data android:name="AA_DB_NAME" android:value="test.db" />
        <meta-data android:name="AA_DB_VERSION" android:value="1" />

        ・・・

</application>

2のやり方は、以下のようにして設定内容を組み立て、起動と停止処理を組み込みます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyApplication extends Application {

  @Override
  public void onCreate() {
    super.onCreate();
    Builder builder = new Configuration.Builder(getBaseContext());
    builder.setCacheSize(1024*1024*4);
    builder.setDatabaseName("test.db");
    builder.setDatabaseVersion(1);
    ActiveAndroid.initialize(builder.create(), true);
  }

  @Override
  public void onTerminate() {
    super.onTerminate();
    ActiveAndroid.dispose();
  }

上記の内容では、以下のものを設定しています。

  • キャッシュサイズ4MB
  • DBを「test.db」
  • バージョンを「1」
  • ActiveAndroidのログを有効化(initializeメソッドのtrueのこと)

個人的には、以下の理由から組み立てて実行するほうがよいかと思います。

  • ActiveAndroidの設定に引きづられず、自分のアプリに影響が出ないこと
  • DBのキャッシュサイズを決められる(デフォルトが1KBのため)
  • progurdによる難読化ができる(AndroidManifest.xmlに書けば見え見えになりますが、コードだと難読化ができる)

尤も、最後のはコード上でstaticに文字列として定義するとあまり意味がないですが・・・

モデル定義


以下のような感じでアノテーションをつける形で定義します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Table(name = "Items")
public class Item extends Model {

  @Column(name = "id", notNull = true)
  public int id;

  @Column(name = "name", unique = true, onUpdate = ForeignKeyAction.NO_ACTION)
  public String name;

  @Column(name = "category", onNullConflict = ConflictAction.REPLACE)
  public String category;

  public List<Test> tests() {
    return getMany(Test.class, "test");
  }
}

アノテーションに付けれる内容は以下の表のような感じのものです。
殆どがAndroid本家のページのConstants(CONFLICT_XXXと記載されたもの)なので
こちらの説明を読んで設定すればよいかと思います。

定義先 定義内容 意味
クラス name 対応するテーブル名
変数 name 対応するカラム名
length 長さの制約(デフォルト -1(無制限?))
notNull not null制約(デフォルト false)
onNullConflict null制約違反時の動作を設定する(デフォルトはFAIL, 設定内容はAndroid公式参照)
onDelete 削除時の外部キーの動作設定(CASCADE, RESTRICT等が設定可能)
onUpdate 更新時の外部キーの動作設定(CASCADE, RESTRICT等が設定可能)
unique ユニーク制約(デフォルト false)
onUniqueConflict ユニーク制約違反時の動作を設定\n(デフォルトはFAIL, 設定内容はAndroid公式参照)

意味が必ずしも正しいかと言われると少し自信がありませんが、多分こんな感じかと。

また、1対Nのような関係を作るときは、getMenyメソッドを実装したヘルパーメソッドを作る必要があるようです。

クエリ

クエリについて、書いていきます。

以下を使ったことがあればすんなり理解できるかと・・・

  • JavaのO/R Mapper(s2jdbc等)
  • RubyのActiveRecord

挿入


以下のような感じで記載します。
ActiveRecordと殆ど同じですね。

1
2
3
Item item = new Item();
item.name = "test";
item.save();

以下のようにすれば、バルクインサートもできます。 また、トランザクションも併せて記載します。

1
2
3
4
5
6
7
8
9
10
11
ActiveAndroid.beginTransaction();
try{
  for(int i = 0; i < 1000; i++ ){
    Item item = new Item();
    item.name = "test_" + i;
    item.save();
  }
  ActiveAndroid.setTransactionSuccessful();
}finally{
  ActiveAndroid.endTransaction();
}

トランザクション中にエラーが出てもthrowされないので、注意して下さい。
コンフリクト周りでエラーが出る場合は、モデルのアノテーションに定義した内容が適用されます。

削除


やり方は、以下の3種類です。

  1. オブジェクトをロードしてから削除する方法
  2. 静的メソッドを使用して削除する方法
  3. クエリを組み立てて削除する方法

1のやり方は、以下です。
一度、selectしてから削除という感じです。

1
2
Item item = Item.load(Item.class, 1);
item.delete();

2のやり方は、以下です。
主キーさえ分かっていれば、こちらのほうが無駄なクエリを発行しない分早いです。

1
Item.delete(Item.class, 1);

3のやり方は、以下です。
複数の行をまとめて削除できたりします。

1
new Delete().from(Item.class).where("Id = ? and name = ?", 1, "test").execute();

参照


これも直感的に記述できます。
使い方は、s2jdbcに似ています。

1
2
3
4
5
Item result = new Select().from(Item.class).executeSingle();

List<Item> resultSet = new Select("id", "name").from(Item.class).execute();

List<Item> resultSet = new Select().from(Item.class).where("Id = ? and name = ?", 1, "test").execute();

上記のような形で実行できます。

  • 先頭1つのカラムデータのみ取得
  • カラムを指定した取り方
  • 条件付き(s2jdcのようにandメソッドとかはありません)

テーブル結合時は、以下のような形で記述します。
現状では、以下の方法しか手段がないようです。

  1. ActiveAndroidでクエリオブジェクト作成
  2. 1で作成したクエリオブジェクトからsqlを作成
  3. 2のsqlを直接実行しCursorクラスのオブジェクトを取得
  4. Cusorクラスからデータを取得する(whileループ等で・・・)
1
2
3
From query = new Select().from(Item.class).innerJoin(Hoge.class).on("Hoge.id = Item.id");

Cursor cursor = Cache.openDatabase().rawQuery(query.toSql(), query.getArguments());

感想


こんな感じな印象でした。簡単なものだと今後も使うかも。

  • ActiveRecordライクなので、扱い易い
  • Modelクラスを継承したクラスを作るだけで、面倒な処理を大幅に軽減できる
  • テーブル定義にコードが引きづられやすくなるので注意
  • 複雑なクエリの場合、通常のSQLiteのDB操作と変わらなくなる

資料


身内向けの勉強会で、ActiveAndroidについて発表してきました。 その時、書いた資料とかも残しておきます。