LoginSignup
3
3

More than 1 year has passed since last update.

【Kotlin研修7日目】SQLiteデータベースを用いたデータ管理

Last updated at Posted at 2021-06-10

Androidにおけるデータ保存

端末内にデータを保存する方法は以下の4通り。

方法 内容
SQLiteデータベース Android OS標準のRDB
プレファレンス キーの簡易データ
→アプリの設定値の保存
内部ストレージ 端末内部ストレージ
外部ストレージ カードスロットに挿入するストレージ

SQLiteデータベースの利用

参考: SQLite
参考: SQLite入門(SQL文)
SQLiteデータベースを利用する手順は、以下の通り。

  1. SQLiteOpenHelperクラスを継承するDatabaseHelper(=DBヘルパーオブジェクト)クラスを作成し、
    SQLiteDatabaseオブジェクト(=DB接続オブジェクト)を生成
  2. アクティビティで、DBヘルパーオブジェクトからDB接続オブジェクトを取得
  3. アクティビティで、DB接続オブジェクトを用いてSQL文を実行
  4. アクティビティ終了時にDBヘルパーオブジェクトを解放

データベースヘルパー(DatabaseHelper)クラスの作成

app/java/<パッケージ名>フォルダにKotlin Fileを新規追加し、
SQLiteOpenHelperクラスを継承するDatabaseHelperクラスを作成する。

SQLiteOpenHelperクラス

サブクラスを作成することで、保持しているDB接続オブジェクトを通じてSQLiteデータベース作成管理を可能にするクラス。
SQLiteOpenHelperのサブクラスオブジェクトはDBヘルパーオブジェクトと呼ばれ、DB接続オブジェクトを所有する。

SQLiteDatabaseクラス

アクティビティSQLiteデータベースの橋渡しを行うクラス。
アクティビティで記述したSQL文を用いてSQLiteデータベースにアクセスし、
実行結果をCursorオブジェクトに格納するDB接続オブジェクト

Cursorクラス

SQL文(SELECT文)の実行結果を形式で格納するカーソルクラス。
Cursorクラスのメソッドを用いてデータを取得する。

定義

DatabaseHelper.kt
class DatabaseHelper(context: Context): SQLiteOpenHelper(
    context: Context?, 
    name: String?, 
    factory: SQLiteDatabase.CursorFactory?. 
    version: Int
) {
    ...
    // 端末内部のSQLiteデータベースのバージョンが古い場合に実行される処理
    // -> 抽象メソッドのため、処理が不要でも記述する必要がある
    override fun onUpgrade(
        db: SQLiteDatabase?, 
        oldVersion: Int, 
        newVersion: Int
    ) {}
}
// パラメータ(SQLiteOpenHelperコンストラクタ)
// context: DatabaseHelperクラスを利用するアクティビティオブジェクト(コンテキスト)
// -> DatabaseHelperクラス側では用意できないため、クラスコンストラクタの引数を利用
// name: DB名
// factory: 手動的に作成するCursorFactoryオブジェクト(通常: null)
// version: DBのバージョン番号(作成時は1で定義)

// パラメータ(onUpgrade()メソッド)
// db: DB接続オブジェクト(後述)
// oldVersion: 内部DBのバージョン番号
// newVersion: コンストラクタで設定したバージョン番号

DB接続オブジェクトの生成

定義したDBヘルパーオブジェクト初期化時(=onCreate())に、
DDL文を用いてSQLiteデータベースを生成する。

DDL(Data Definition Language)

SQLiteデータベース生成削除を行うSQL文

CREATE文によるDB生成

CREATE文
CREATE TABLE <テーブル名> (
    _id INTEGER PRIMARY KEY,  -- 主キーとなるID(INTEGER型)
    /* 主キーによってレコード(=行)が特定できる */
    <カラム名1> TEXT,  -- カラム①(TEXT型)
    <カラム名2> BOOLEAN,  -- カラム②(BOOLEAN型)
    <カラム名3> DOUBLE,  -- カラム③(DOUBLE型)
    <カラム名4> DATE,  -- カラム④(DATE型)
    <カラム名5> BLOB  -- カラム⑤(BLOB型)
);

定義

SQLiteDatabase?.execSQL(sql: String!): Unit
// パラメータ
// sql: DDL文

サンプルコード

DatabaseHelper.kt
// SQLiteデータベースを生成するクラス
// -> SQLiteOpenHelperのコンテキストを代入するため、
//    クラスコンストラクタにContextを必要とする
class DatabaseHelper(context: Context): SQLiteOpenHelper(
    context,
    DATABASE_NAME,
    null,
    DATABASE_VERSION
) {
    // private定数の定義
    companion object {
        // DB名
        private const val DATABASE_NAME = "itemmemo.db"
        // 生成時のDBバージョン番号
        private const val DATABASE_VERSION = 1
    }

    // 初期化時に実行される処理
    // -> DatabaseHelperクラスの初期化と同時に、
    //    保持しているSQLiteDatabaseオブジェクトを通じてSQLiteデータベースを生成
    override fun onCreate(db: SQLiteDatabase?) {
        // DDL文
        // -> 改行によって可読性を高めるため、append()ができるStringBuilderを用いる
        // StringBuilder: 文字列を格納する(同一ポインタ内での)可変配列を定義するクラス
        val sb = StringBuilder()

        // DDL文の定義
        sb.append("<DBを作成するDDL文>")
        ...   // DDL文に応じてappend()の繰り返し

        // DDL文をString型に変換
        val sql = sb.toString()

        // SQLiteデータベースの生成
        db?.execSQL(sql)
    }
    ...
}

SQLiteDatabaseオブジェクトの取得

DBヘルパーオブジェクトが所有するDB接続オブジェクトには、プロパティを通じてアクセスすることができる。

定義

// 読み書き可能なSQLiteDatabaseオブジェクト
val db = SQLiteOpenHelper.writableDatabase

// 読み込み専用のSQLiteDatabaseオブジェクト
// -> データベースにデータが書き込めない場合などに用いる
val db = SQLiteOpenHelper.readableDatabase

サンプルコード

MainActivity.kt
class MainActivity : AppCompatActivity() {
    // DatabaseHelperオブジェクトの生成
    // -> クラスコンストラクタを定義しているため、
    //    DatabaseHelperクラスを利用するコンテキストを指定
    // <- 様々な処理で使用するため、処理直前ではなく事前に生成
    private val _helper = DatabaseHelper(this@MainActivity)

    // SQLiteDatabaseオブジェクト(DB接続オブジェクト)の指定
    // SQLiteOpenHelper.writableDatabase: 読み書き可能なSQLiteDatabaseオブジェクト
    val db = _helper.writableDatabase
    ...
}

DBヘルパーオブジェクトの解放

SQLiteデータベースを利用するアクティビティの終了(=onDestroy())時、
close()メソッドを用いてDBヘルパーオブジェクトを解放する必要がある。

定義

SQLiteOpenHelper.close()

サンプルコード

MainActivity.kt
class MainActivity : AppCompatActivity() {
    // DatabaseHelperオブジェクトの生成
    private val _helper = DatabaseHelper(this@MainActivity)

    // アクティビティ終了時に実行する処理
    override fun onDestroy() {

        // DatabaseHelperオブジェクトの解放
        // SQLiteOpenHelper.close(): Databaseオブジェクトの解放
        _helper.close()

        // アクティビティの終了
        super.onDestroy()
    }
}

SQL文によるデータベースへのアクセス

SQL文を用いてデータベースにアクセスする手順は、データ更新処理データ取得処理によって異なる。

データ更新処理

データ更新処理を行う手順は、以下の通り。

  1. DML文SQLiteDatabaseクラスのメソッドを用いてコンパイルを実行
  2. 1.でバインド変数(後述)を用いた場合は値をバインド
  3. コンパイルしたDML文を実行

DML文のコンパイル

コンパイルされたDML文は、ステートメントオブジェクトに変換される。
ステートメントオブジェクトSQLiteStatementクラスで定義される。

DML(Data Manipulation Language)

テーブルに対して、データの取得追加更新削除を行うSQL文

SQLiteStatementクラス

ステートメントオブジェクトを定義するクラス。
SQLiteStatementクラスのメソッドを用いて、定義したDML文を実行することができる。

定義

SQLiteDatabase.compileStatement(sql: String!): SQLiteStatement!
// sql: SQL文

サンプルコード

MainActivity.kt
class MainActivity : AppCompatActivity() {
    // DatabaseHelperオブジェクトの生成
    private val _helper = DatabaseHelper(this@MainActivity)

    // ボタンの"タップ"イベント検知時の処理(イベントハンドラ)
    fun onSaveButtonClick(view: View) {
        // SQLiteDatabaseオブジェクト(DB接続オブジェクト)の指定
        val db = _helper.writableDatabase

        // 更新前データを削除するSQL文
        // ?: 変数が後から挿入される箇所(=バインド変数)
        val sqlDelete = "DELETE FROM itemmemos WHERE _id = ?"

        // 更新前データを削除するSQL文のコンパイル
        var stmt = db.compileStatement(sqlDelete)

        ...
    }
}

バインド変数への値のバインド

バインド変数(=?)を用いた場合は、SQLiteStatementクラスが継承している、
SQLiteProgramクラスのbind<T>()メソッドを用いてバインド変数に値をバインドする。
<T>プレースホルダ

バインド変数

後からSQL文中に値をバインドできる変数で、?によって表される。

SQLiteProgram

コンパイルされたSQL文を実行するSQLiteStatementクラスの基本クラス。

定義

SQLiteProgram.bind<T>(index: Int, value: <T>): Unit
// パラメータ
// index: SQL文の"?"のインデックス番号
// -> 1番目は"1", 2番目は"2", ..., n番目は"n"
// value: SQL文の"?"にバインドする値

サンプルコード

MainActivity.kt
// 更新前データを削除するSQL文
// ?: 変数が後から挿入される箇所(=バインド変数)
val sqlDelete = "DELETE FROM itemmemos WHERE _id = ?"

// 更新前データを削除するSQL文のコンパイル
var stmt = db.compileStatement(sqlDelete)

// バインド変数への値のバインド
stmt.bindLong(1, _itemId.toLong())

// -------------------------------

// 更新後データを挿入するSQL文
// ?: 変数が後から挿入される箇所(=バインド変数)
val sqlInsert = "INSERT INTO itemmemos (_id, item, note) VALUES (?, ?, ?)"

// 更新後データを挿入するSQL文のコンパイル
stmt = db.compileStatement(sqlInsert)

// バインド変数への値のバインド
stmt.bindLong(1, _itemId.toLong())
stmt.bindString(2, _item)
stmt.bindString(3, note)

SQL文(DML文)の実行

コンパイルされたSQL文(=ステートメントオブジェクト)は、
更新・削除を行うexecuteUpdateDelete()メソッドと、
追加(=挿入)を行うexecuteInsert()メソッドを用いて実行する。

DELETE, INSERT, UPDATE文によるテーブルの更新

-- データの削除
DELETE FROM <テーブル名> WHERE <条件式>;

-- データの挿入(追加)
/* 全てのカラムにデータを追加する場合 */
INSERT INTO <テーブル名> VALUES (<追加するデータ>)
/* 特定のカラムにデータを追加する場合 */
INSERT INTO <テーブル名> (<カラム名>) VALUES (<追加するデータ>);

-- データの更新
UPDATE <テーブル名> SET <カラム名>=<更新後のデータ> WHERE <条件式>;

定義

// データの更新・削除
// -> 返り値はSQL文の実行によって変更された行の数
SQLiteStatement.executeUpdateDelete(): Int

// データの追加(挿入)
// -> 返り値はSQL文の実行によって変更された行の主キー
SQLiteStatement.executeInsert(): Long

サンプルコード

MainActivity.kt
// データの(更新・)削除

// 更新前データを削除するSQL文(DML文)
val sqlDelete = "DELETE FROM itemmemos WHERE _id = ?"

// 更新前データを削除するSQL文のコンパイル
var stmt = db.compileStatement(sqlDelete)

...  // 値のバインド処理

// (更新・削除を行う)DML文の実行
stmt.executeUpdateDelete()

// -------------------------------

// データの追加(挿入)

// 更新後データを挿入するSQL文(DML文)
val sqlInsert = "INSERT INTO itemmemos (_id, item, note) VALUES (?, ?, ?)"

// 更新後データを挿入するSQL文のコンパイル
stmt = db.compileStatement(sqlInsert)

...  // 値のバインド処理

// (挿入を行う)SQL文の実行
stmt.executeInsert()

データ取得処理

データ取得処理を行う手順は、以下の通り。

  1. SQLiteDatabaseクラスのメソッドを用いてDML文を実行し、
    実行結果をCursorオブジェクトに格納
    コンパイル不要
    ※バインド変数を用いる場合は、実行時にバインド処理を行う
  2. カーソルループ処理走査しながら、各レコードのデータを取得

SQL文(DML文)の実行、Cursorオブジェクトへの格納

SELECT文によるデータの取得

SELECT <カラム名> FROM <テーブル名> WHERE <条件式>;

定義

SQLiteDatabase.rawQuery(
    sql: String!, 
    selectionArgs: Array<String!>!
): Cursor!
// パラメータ
// sql: 実行するSQL文
// selectionArgs: バインド変数用のString型配列
// -> バインド変数を利用しない場合はnull
// 返り値はSQL文の実行結果表を格納するCursorオブジェクト

サンプルコード

MainActivity.kt
// SQLiteDatabaseオブジェクト(DB接続オブジェクト)の指定
val db = _helper.writableDatabase

// データを取得するSQL文
val sql = "SELECT * FROM itemmemos WHERE _id = ${_itemId}"

// SQL文(SELECT文)の実行結果表を格納するCursorオブジェクト
val cursor = db.rawQuery(sql, null)

カーソルの走査によるデータの取得

SQL文(SELECT文)によって得られた実行結果は、
形式でCursorオブジェクト(=カーソル)に格納されているため、
カーソルレコード(=行)単位で走査することで実行結果を参照する。

走査を行う中で、カーソルがもつテーブルから、指定したカラム(=列)以外を排除しながら
条件に一致するレコードインデックス番号を取得できるgetColumnIndex()メソッドを用いて、
カーソルから取得するデータを抽出する。

定義

// 指定したカラム(=列)名に一致するレコード(=行)の、Index番号を取得
// -> 該当レコードが存在しない場合は-1を返却
// <- 指定したカラム以外のカラムは排除される
Cursor.getColumnIndex(columnName: String!): Int
// パラメータ
// columnName: カラム名

// データの取得
Cursor.get<T>(columnIndex: Int): <T>
// パラメータ
// columnIndex: 取得する列のIndex番号

サンプルコード

MainActivity.kt
// 取得する文字列
// -> データが存在しない場合に備えて空文字で初期化
var note = ""

// SQL文(SELECT文)の実行結果表を格納するCursorオブジェクト
val cursor = db.rawQuery(sql, null)

// ループによるデータ取得
// Cursor.moveToNext(): 次の行に移動
// -> 次の行が存在する場合はtrue, 存在しない場合はfalseを返す
// <- 最初はテーブルの0行目に位置しているため、
//    while構文を用いて最初に1行目に移る処理を行う
while (cursor.moveToNext()) {

    // 列名を指定してIndex番号を取得
    // <- 指定したカラム以外のカラムは排除される
    val idxNote = cursor.getColumnIndex("note")

    // 文字列の取得
    note = cursor.getString(idxNote)
}

バインド変数を用いたデータ取得

サンプルコード

MainActivity.kt
// 発展:

// データを取得するSQL文
val sql = "SELECT * FROM itemmemos WHERE _id = ?"

// バインドする値を格納した配列
val params = arrayOf(_itemId.toString())

// SQL文(SELECT文)の実行結果表を格納するCursorオブジェクト
val cursor = db.rawQuery(sql, params)

SQL文を記述せずにデータを取得

定義

SQLiteDatabase.query(
    table: String!, 
    columns: Array<String!>!, 
    selection: String!,
    selectionArgs: Array<String!>!, 
    groupBy: String!, 
    having: String!, 
    orderBy: String!
): Cursor!
// パラメータ
// table: データベース名
// columns: 取得する列名を格納したString型配列
// -> nullの場合は全ての列を取得
// selection: 取得する行の条件
// selectionArgs: バインド変数用のString型配列
// -> バインド変数を利用しない場合はnull
// groupBy: グループ化条件
// -> nullの場合はグループ化を行わない
// having: 抽出するグループ条件
// -> nullの場合はグループ化を行わない
// orderBy: ソート条件
// -> nullの場合はソートを行わない
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3