Androidアプリ開発 SQLite トランザクション処理

2016年12月11日(編集2016年12月11日)
このエントリーをはてなブックマークに追加

Androidアプリ開発では、データベースにSQLiteが利用できます。
この記事は、AndroidアプリのSQLiteでトランザクション処理を実装する方法を記載した記事です。

環境はAndroid 7.1 (API level 25) です。

環境

  • OS X El Capitan
  • android sdk 25
  • Oracle jdk version 1.8.0_72
  • Android Studio 2.2.2

難易度

初心者向け

サンプルコード

Android-SQLite-Demo

トランザクション

トランザクションは、SQL処理で分割することのできない、一連の情報処理の単位のことです。
トランザクションを使うのは、途中で処理が中断した場合、データが中途半端な状態で登録されるのを防ぐためです。

図1

上記の図1は、insert1, update1, insert2がまとまった処理です。
つまり、update1やinsert2でエラーが発生し、処理に失敗してしまうと、データが不整合となります。
この場合、insert1、update1、insert2が、ひとまとめの処理として実行されなければいけません。
こういった場合に、一連の処理をトランザクションにして対応します。

SQLite

AndroidのSQLiteは、トランザクションを利用できます。
例として、上記の図1のトランザクションを実装してみましょう。

実装

AndroidでSQLiteを使う場合は、SQLiteDatabaseクラスを使います。
SQLiteDatabaseクラスは、直接操作するより、Helperクラスを用意した方が使いやすくなります。

{project_folder}/util/DBHelper.java
public class DBHelper {
    public static final String TAG = "DBHelper";

    public SQLiteDatabase db;
    private final DBOpenHelper dbOpenHelper;

    public DBHelper(final Context context) {
        this.dbOpenHelper = new DBOpenHelper(context);
        establishDb();
    }

    private void establishDb() {
        if (this.db == null) {
            this.db = this.dbOpenHelper.getWritableDatabase();
        }
    }

    public void cleanup() {
        if (this.db != null) {
            this.db.close();
            this.db = null;
        }
    }

    /**
     * True if Database can be deleted. False if not available
     *
     * @param context
     * @return
     */
    public boolean isDatabaseDelete(final Context context) {
        boolean result = false;
        if (this.db != null) {
            File file = context.getDatabasePath(dbOpenHelper.getDatabaseName());
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                result = this.db.deleteDatabase(file);
            }
        }
        return result;
    }
}
        

上記は、AndroidでSQLiteを操作するための基本的な処理を記述したhelperクラスです。
もしlockが発生するような処理をする場合は、Singletonを使いましょう。

ここでは、上記のシンプルなHelperクラスを使います。

トランザクションの開始

トランザクションの開始を実装します。
トランザクションの開始は、全ての処理の前に行う必要があります。

図2

図2の赤線の部分をコードで記述します。

this.db.beginTransaction();
        

db変数は、SQLiteDatabaseクラスです。SQLiteDatabase#beginTransactionメソッドを呼ぶことで、トランザクションが開始されます。

ヘルパークラスでは以下のように記載します。

public void beginTransaction() {
    if (this.db != null) {
        this.db.beginTransaction();
    }
}
        

トランザクション終了

トランザクションの終了を実装します。

図3

図3の赤線の部分をコードで記述します。

this.db.endTransaction();
        

endTransactionメソッドを呼ぶと、beginTransactionメソッドで開始したトランザクションが終了します。beginTransactionメソッドを使ったら、endTransactionは必ず呼び出す必要があります。なので、try-finally文で使います。

トランザクション後に成功処理を呼び出さないでendTransactionを呼び出した場合、トランザクション間の処理は保存されません。つまり、ロールバック処理になります。

ヘルパークラスでは以下のように記載します。

public void endTransaction() {
    if (this.db != null) {
        this.db.endTransaction();
    }
}
        

トランザクション成功

全ての処理が正常終了した後、トランザクションに成功通知をする必要があります。

図4

図4の赤線の部分をコードで記述します。

this.db.setTransactionSuccessful();
        

setTransactionSuccessfulメソッドを実行すると、beginTransactionメソッドで開始した処理が成功したというマークが記録されます。

beginTransactionメソッドで開始したトランザクションは、setTransactionSuccessfulメソッドか、endTransactionメソッドを呼び出すまでDatabaseに対してなにも行いません。

ヘルパークラスでは以下のように記載します。

public void setTransactionSuccessful() {
    if (this.db != null) {
        this.db.setTransactionSuccessful();
    }
}
        

ロールバック

AndroidのSQLiteにロールバック処理はありません。処理に失敗した場合は、beginTransactionメソッドの後にendTransactionメソッドを呼び出します。

図5

図5の場合、成功したinsert 1の処理は反映されません。

トランザクションは、setTransactionSuccessfulメソッドが呼び出されなければ、コミットされません。

トランザクション処理実装

上記を踏まえると、以下のようなコードが記述できます。

{project_folder}/model/DauHelper.java
    /**
     * execute transaction
     *
     * @param context
     * @return
     */
    public static long executeTransaction(final Context context) {
        long result = -1;
        DBHelper dbhelper = new DBHelper(context);
        try {
            // start transaction
            dbhelper.beginTransaction();
            // insert
            result = dbhelper.db.insert(Dau.TABLE_NAME, Dau.COL.get(0) + " = " + id, null);
            // update
            result = dbhelper.db.update(Dau.TABLE_NAME, new ContentValues(), Dau.COL.get(0) + " = " + id, null);
            // insert
            result = dbhelper.db.insert(Dau.TABLE_NAME, Dau.COL.get(0) + " = " + id, null);
            // transaction Successful
            dbhelper.setTransactionSuccessful();
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            result = -1;
        } finally {
            // end transaction
            dbhelper.endTransaction();
            dbhelper.cleanup();
        }
        return result;
    }
        

上記のコードで、トランザクションを意識した処理を実行することができます。

実装のポイント

実装のポイントは、setTransactionSuccessfulメソッドとendTransactionの処理です。

このどちらかのメソッドを呼び出すことで、beginTransactionメソッドで開始したトランザクションが終了します。
逆に言うと、これらの処理を呼び出さないと、トランザクション状態が続きます。必ずfinally処理でendTransaction処理を実行するようにします。

結論

トランザクションを使うことで、データの整合性をコントロールできるようになります。
質の高いアプリを作るためにも、ぜひ利用していきましょう。

関連記事

タグ検索で調べてみよう

Android7.0 SQLite