2012/07/31

Android:bulkInsertとapplyBatchのアトミック性を保証する

●blukInsert

ContentResolverやContentProviderClientにあるbulkInsertはアトミックな操作ではありません。
トランザクションの開始なしに、連続してinsertするAPIです。
ContentResolver.bulkInsert

・android.content.ContentProvider.bulkInsert(Uri, ContentValues[])
/**
 * Override this to handle requests to insert a set of new rows, or the
 * default implementation will iterate over the values and call
 * {@link #insert} on each of them.
 * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
 * after inserting.
 * This method can be called from multiple threads, as described in
 * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
 * and Threads</a>.
 *
 * @param uri The content:// URI of the insertion request.
 * @param values An array of sets of column_name/value pairs to add to the database.
 * @return The number of values that were inserted.
 */
public int bulkInsert(Uri uri, ContentValues[] values) {
    int numValues = values.length;
    for (int i = 0; i < numValues; i++) {
        insert(uri, values[i]);
    }
    return numValues;
}

bulkInsertのアトミック性を保証したい場合、独自のプロバイダでbulkInsertをオーバーライドします。
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
    SQLiteDatabase db = MyDataBase.getInstance(getContext())
            .getWritableDatabase();
    db.beginTransaction();
    try {
        SQLiteStatement insertStmt = db
                .compileStatement("INSERT INTO test "
                        + "(name) VALUES (?);");
        for (ContentValues value : values) {
            insertStmt.bindString(1, value.getAsString("name"));
            insertStmt.executeInsert();
        }
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();
    }
    ...
これでbulkInsertのアトミック性が保証されます。


●applyBatch

blukInsertと同じく。
ContentResolverやContentProviderClientにあるapplyBatchもアトミックな操作ではありません。
まとめてContentProviderOperationを実行するためのAPIで、トランザクションは開始されません。
ContentResolver.applyBatch

・android.content.ContentProvider.applyBatch(ArrayList<ContentProviderOperation>)
/**
 * Override this to handle requests to perform a batch of operations, or the
 * default implementation will iterate over the operations and call
 * {@link ContentProviderOperation#apply} on each of them.
 * If all calls to {@link ContentProviderOperation#apply} succeed
 * then a {@link ContentProviderResult} array with as many
 * elements as there were operations will be returned.  If any of the calls
 * fail, it is up to the implementation how many of the others take effect.
 * This method can be called from multiple threads, as described in
 * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
 * and Threads</a>.
 *
 * @param operations the operations to apply
 * @return the results of the applications
 * @throws OperationApplicationException thrown if any operation fails.
 * @see ContentProviderOperation#apply
 */
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
        throws OperationApplicationException {
    final int numOperations = operations.size();
    final ContentProviderResult[] results = new ContentProviderResult[numOperations];
    for (int i = 0; i < numOperations; i++) {
        results[i] = operations.get(i).apply(this, results, i);
    }
    return results;
}

applyBatchのアトミック性を保証したい場合、独自のプロバイダでapplyBatchをオーバーライドします。
@Override
public ContentProviderResult[] applyBatch(
        ArrayList<ContentProviderOperation> operations)
        throws OperationApplicationException {
    SQLiteDatabase db = MyDataBase.getInstance(getContext())
            .getWritableDatabase();
    db.beginTransaction();
    try {
        ContentProviderResult[] result = super.applyBatch(operations);
        db.setTransactionSuccessful();
        return result;
    } finally {
        db.endTransaction();
    }
}
これでapplyBatchのアトミック性が保証されます。

以上です。