Android で SQLite を使う

概要

AndroidSQLite を使う方法です。 サンプルにはデータの参照、挿入、トランザクションを含みます。

参考情報

サンプルコード

解説

  • Database.kt
    • Database : SQLiteOpenHelper を実装して汎用的な処理を行うクラス
  • SQLiteDatabaseExtensions.kt : update, insert メソッドの引数を省略できるようにするための拡張関数
  • SampleDB.kt
    • SampleDB : サンプルデータベースの定義
    • SampleDB.Dao : サンプルデータベースが持つ DAO の一覧
  • SampleTableDao.kt
    • SampleTableDao : DAO (Data Access Object)
  • Client.kt
    • Client : 上記のクラスを使用して処理を行うクラス

AndroidSQLite を使うには SQLiteOpenHelper を使用する。 Database にて SQLiteOpenHelper を使用している。 データベースファイルが存在しない場合には onCreate メソッドが呼ばれるため、onCreate メソッドが呼ばれたらテーブルを作成する。 テーブルの作成は SampleDB.Dao に処理を委譲して実現している。 SampleDB.Dao は SampleTableDao に処理を委譲する。 複数の DAO がある場合には、複数の DAO に処理を委譲する。 データベースのバージョンが変更された場合には onUpgrade メソッドが呼ばれるが、今回のサンプルでは未実装。

// Database
private val helper = object : SQLiteOpenHelper(context, def.name, def.cursorFactory, def.version) {

    override fun onCreate(db: SQLiteDatabase) {
        def.create(db).onCreate()
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {}
}

CRUD 操作は、SQLiteOpenHelper から readableDatabase と writableDatabase を取得して行う。 readableDatabase と writableDatabase は共に SQLiteDatabase のインスタンス。 SQLiteDatabase は query メソッドや insert メソッドを持つ。 query メソッドや insert メソッドの呼び出しは、SampleTableDao の仕事。 query メソッドと insert メソッドは多くの引数を持ち、全ての引数を指定する必要のない場合がある。 引数を省略できるようにするために SQLiteDatabaseExtensions.kt で拡張関数を定義している。

// Database
helper.readableDatabase.use {
    ...
} // use 関数により readableDatabase は close() される

helper.writableDatabase.use {
    ...
} // use 関数により writableDatabase は close() される
// SampleTableDao
fun insert(seqId: Int, time: Int, type: Int) {
    val values = ContentValues().apply {
        put("seq_id", seqId)
        put("time", time)
        put("type", type)
    }
    db.insert(TABLE_NAME, values) // SQLiteDatabaseExtensions.kt で定義された拡張関数
}

Client では CRUD 操作を行う関数を定義し、Database の update メソッドに渡す。 関数のブロックがトランザクションに対応する。 例外を発生させることなく関数を終えた場合にはコミットされ、例外が発生した場合にはロールバックされる。 トランザクション管理は Database で行っている。 update メソッドでは、関数実行前に beginTransaction() を実行、実行後に endTransaction() を実行している。 例外が発生しなかった場合には setTransactionSuccessful() を実行してコミットされるようにする。

// Database
// it は SQLiteDatabase のインスタンス
it.beginTransaction()
try {
    val result = procedure(def.create(it))
    it.setTransactionSuccessful()
    return result
}
finally {
    it.endTransaction()
}

update メソッドが受け取る関数では、引数に SampleDB.Dao を受け取る。 SampleDB.Dao が保持する SampleTableDao に処理を委譲することで CRUD 操作が行われる。 テーブルが増えた場合には SampleDB.Dao に各テーブルの DAO を加える。

// Client
db.update { // トランザクション開始
    // it は SampleDB.Dao のインスタンス
    val seqId = it.sampleTable.nextSeqId()
    it.sampleTable.insert(seqId, time, type)
} // トランザクション終了 (例外が発生しなければコミットされる)

Kotlin 補足

Kotlin は全く知らないという方のための補足。

use 関数

下記のコードは同じこと。

// Kotlin
helper.writableDatabase.use {
    it.beginTransaction()
    ...
}
// Java
try (SQLiteDatabase it = helper.getWritableDatabase()) {
    it.beginTransaction()
    ...
}