2012/08/01

Android:ContentProviderOperationとapplyBatch


データベースへのクエリをまとめて発行したい場合はapplyBatchの使用を検討します。

●applyBatchのメリット

・ContentResolver経由で1つずつクエリ実行するより高速
これは、ContentResolver.applyBatch内部でContentProviderClientを使用するためです。

引用:android.content.ContentResolver.applyBatch(String, ArrayList<ContentProviderOperation>)
public ContentProviderResult[] applyBatch(String authority,
        ArrayList<ContentProviderOperation> operations)
        throws RemoteException, OperationApplicationException {
    ContentProviderClient provider = acquireContentProviderClient(authority);
    if (provider == null) {
        throw new IllegalArgumentException("Unknown authority " + authority);
    }
    try {
        return provider.applyBatch(operations);
    } finally {
        provider.release();
    }
}
ContentProviderClientによる高速化については過去の記事で取り上げています。
高速化。ContentResolver?ContentProviderClient?

・トランザクション制御が容易
デフォルトではapplyBatchのアトミック性は保証されません。
これを保証するにはContentProviderのapplyBatchをオーバーライドしてトランザクション処理を追加します。
bulkInsertとapplyBatchのアトミック性を保証する


●applyBatchのデメリット

・クエリの組み立てが若干複雑
クエリの組み立て方に独特なルールがあります。
また、後方参照を使用しなければならないケースでは可読性が下がります。
といっても、理解すれば問題ないレベルです。

・クエリが途中で失敗した場合の対処
まず、applyBatchがアトミック性を保証していないということ。
さらに、クエリが途中で失敗すると、それ以降のクエリは実行されないということ。
この2点を念頭においてapplyBatchを使用する必要があります。

applyBatchのメリット・デメリットを列挙したところで、本稿ではapplyBatchの概要と、これを使用するためのContentProviderOperationについて記載します。


●概要

applyBatchはクエリ群をContentProviderOperationのリストとして受け取ります。
ContentProviderOperationはinsert/update/deleteクエリを表現するクラスです。
クエリは、ContentProviderOperationに設定した値をもとに生成・発行されます。
ContentResolver.applyBatch  (String, ArrayList<ContentProviderOperation>)
ContentProviderOperation


●ContentProviderOperation.Builder

ContentProviderOperationはContentProviderOperation.Builderクラスから生成します。
ContentProviderOperation.Builder

ContentProviderOperationを構築する簡単なサンプルは下記です。
// Insert文のオペレーションを生成
ContentProviderOperation.newInsert(uri).withValue("name", "test1").build()

// Update文のオペレーションを生成
ContentProviderOperation.newUpdate(uri).withValue("name", "test2").build()

// Delete文のオペレーションを生成
ContentProviderOperation.newDelete(uri).build()
ContentProviderOperation.Builderは用途にあわせてInsert用、Update用、Delete用が用意されています。
ビルダは"どの種類のビルダなのか"を明示してインスタンスを生成します。
  • newInsert(uri) : Insert用ビルダを生成
  • newUpdate(uri) : Update用ビルダを生成
  • newDelete(uri) : Delete用ビルダを生成


●ContentProviderOperationの構築

ContentProviderOperation.Builderには、ContentProviderOperationを構築するためのメソッドが用意されています。
各メソッドはInsert文に特化したものや、Update/Delete文に特化したものがあります。
例えばInsert用ビルダでは、Update/Deleteに特化したwithSelection(...)メソッドは使用できません。
各ビルダ種別毎の使用可能/不可能メソッドの一覧はContentProviderOperation.Builderのjavadocで確認できます。
もし、使用不可能なメソッドを呼び出すと例外が投げられます。
# Insert用ビルダがwithSelectionメソッドを呼び出した場合
java.lang.IllegalArgumentException: 
        only updates, deletes, and asserts can have selections

主なメソッドを下記に列挙します。
InsertやUpdateクエリで追加値・更新値を指定するには下記のメソッドを使用します。
  • ContentProviderOperation.Builder.withValues(ContentValues) 
  • ContentProviderOperation.Builder.withValueBackReference(String, int)
  • ContentProviderOperation.Builder.withValueBackReference(ContentValues)
これらはDelete用ビルダでは使用できないメソッド群です。

***BackReferenceは後方参照です。これについては次回投稿します。
withValuesメソッドを使用した簡単なサンプルは下記です。
// nameにtest1を持つレコードを追加するInsert文
ContentProviderOperation.newInsert(uri).withValue("name", "test1").build();
ContentProviderOperationで表現されるクエリは条件(selection)の指定が可能です。
条件指定には下記のメソッドを使用します。
  • ContentProviderOperation.Builder.withSelection(String, String[]) 
  • ContentProviderOperation.Builder.withSelectionBackReference(int, int) 
これらはInsert用ビルダでは使用できないメソッド群です。

****BackReferenceは後方参照です。これについては次回投稿します。
withSelectionメソッドを使用した簡単なサンプルは下記です。
// _idが3のレコードのnameを"test5"に更新するUpdate文
ContentProviderOperation.newUpdate(uri).withValue("name", "test5")
    .withSelection("_id=?", new String[]{"3"}).build();

●ContentProviderOperationの結果オブジェクト

applyBatchは複数のクエリ(ContentProviderOperation)を実行します。
そのため、複数のクエリ結果をContentProviderResultの配列で返します。

ContentProviderResultはuriとcountを持つシンプルなクラスです。
Insertクエリの結果はuriに、Update/Deleteの結果はcountに格納されます。
ContentProviderResult

applyBatchで実行される各クエリの結果を参照したい場合はContentProviderResultを参照します。


●applyBatchの実行

applyBatchを実行する簡単なコードは下記です。
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
Uri uri = Uri
        .parse("content://yuki.contentprovider.mycontentprovider");
operations.add(ContentProviderOperation.newInsert(uri)
        .withValue("name", "test1").build());
operations.add(ContentProviderOperation.newUpdate(uri)
        .withValue("name", "test2").build());
try {
    getContentResolver().applyBatch(
            "yuki.contentprovider.mycontentprovider", operations);
} catch (Exception e) {
    Log.e("yuki", "error");
}
これを実行すると、Insert文とUpdate文がまとめて実行されます。

クエリはArrayList(operations)の先頭から順番に実行されます。
もし途中でクエリの実行が失敗すると、それ以降のクエリは実行されません。
# applyBatchにアトミック性を求めるなら「bulkInsertとapplyBatchのアトミック性を保証する」を参照

以上です。