(38) RoomでSQLiteを簡単に使う。

投稿者: | 2023年3月14日

222 views

この記事は最終更新から 431日 が経過しています。

1. やりたいこと

Androidアプリで簡易データベースである SQLiteを使いたい。
制御が簡単(?)な Roomライブラリを使って実現してみた。

Roomとは?
については Android公式サイトで丁寧に解説されているのでこちらを参照されたい。
https://developer.android.com/training/data-storage/room?hl=ja

2. やってみる

UIから操作しながら動作確認をしたかったので、以下のような画面を持つアプリを作った。
・[登録]ボタンを押下すると、EditText欄に入力した文字列を DBに書き込む。
・[全消去]ボタンを押下すると、DBに保存されているすべてのデータを消去する。
・[DB削除]ボタンを押下すると、DB自体を削除する。(開発作業中に DBを作り直したい場合があるので…)

以下の 4個のプログラムソースファイルで実装した。
1) AppDatabase.java
2) Memo.java
3) MemoDao.java
4) MainActivity.java

(0) build.gradle (:app)

前述の Android公式サイト に書かれている通りに、アプリの build.gradle ファイルに次の依存関係を追加する。
※記述後の Sync.now を忘れずに!

dependencies {
    def room_version = "2.5.0"
    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"

(1) AppDatabase.java

データベース本体を定義する。

package com.example.testroomsqlite;

import androidx.room.Database;
import androidx.room.RoomDatabase;

@Database(entities = {Memo.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
    public abstract MemoDao memoDao();
}

(2) Memo.java

テーブルレコードになるデータ、すなわちエンティティ(Entity)を定義する。

主キーに AUTO_INCREMENT を付与したい場合、主キーのアノテーションの右側に以下のように書き足す。

@PrimaryKey(autoGenerate = true)
package com.example.testroomsqlite;

import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity
public class Memo {
    public Memo(){
    }

    @PrimaryKey(autoGenerate = true)
    public int mid;

    @ColumnInfo(name = "memo_content")
    public String memoContent;

    @ColumnInfo(name = "memo_datetime")
    public String memoDateTime;

    public String getMemoContent(){
        return this.memoContent;
    }

    public void setMemoContent(String memoContent){
        this.memoContent = memoContent;
    }
}

(3) MemoDao.java

memoテーブルへの DAO(Data Access Object)を定義する。
・@Queryアノテーションを書いた場合、その右側にSQLコマンドを書く。
・insertメソッドの戻り値を voidではなく longにすると、上記の @PrimaryKey(autoGenerate=true) で付与された midの値が返される。

package com.example.testroomsqlite;

import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;

@Dao
public interface MemoDao {
    @Query("SELECT * FROM memo")
    List<Memo> getAll();

    @Insert
    long insert(Memo memo);
    // 戻り値: 自動的に割り当てられた主キー Memo#mid の値が返る。

    @Delete
    void delete(Memo memo);

    // SQLiteでは DROPコマンドが使えない。→ DELETE FROMで全削除する。
    @Query("DELETE FROM memo")
    void deleteAll();
}

(4) MainActivity.java

アプリ本体の制御を書く。

注意点はただ一つ、それは…
DB制御は非UIスレッドに書くこと!

でないと、以下のように例外が発生し、アプリがエラー終了してしまう。

Caused by: java.lang.IllegalStateException: Cannot access database on the main thread
since it may potentially lock the UI for a long period of time.

長時間 UIがロックされる可能性があるため、メインスレッドでDBアクセスはダメ
とのことだ。

よって、下記のプログラムソースコードでは、
DBアクセスは、別スレッドを起動して実行している。

package com.example.testroomsqlite;

import androidx.appcompat.app.AppCompatActivity;
import androidx.room.Room;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import java.util.List;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {
    private static AppDatabase dbInst = null;               // DB
    private final static String dbName = "my-database";     // DB名称
    private List<Memo> memoList;                            // DBからロードしたメモデータ
    private final Handler hMainThread = new Handler();      // Main threadのハンドル

    // DBインスタンスを取得
    public static AppDatabase getDb(Context context) {
        if (dbInst == null) {
            dbInst = Room.databaseBuilder(context, AppDatabase.class, dbName).build();
        }
        return dbInst;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.UpdateText();      // DBからロードしたデータで表示を更新
    }

    // DBからロードしたデータで表示を更新
    private void UpdateText() {
        // DBデータ読み出しのスレッドを生成&実行
        // ※DBアクセスは非UIスレッド(=非Mainスレッド)で実行する必要がある。
        // ※スレッドインスタンスの再利用はできない → 毎度インスタンスを生成する。
        Thread th = new Thread(runRecallFromDB);
        th.start();
    }

    // DBデータ読み出しスレッドの実処理
    private final Runnable runRecallFromDB = new Runnable() {
        @Override
        public void run() {
            System.out.println("BEGIN: runRecallFromDB");
            AppDatabase db = getDb(getApplicationContext());
            MemoDao dao = db.memoDao();         // DAOを生成
            memoList = dao.getAll();            // DBからデータをロード
            hMainThread.post(runUpdateText);    // UIスレッドに表示を依頼
            System.out.println("END: runRecallFromDB");
        }
    };
    // UIスレッドでの表示更新処理
    private final Runnable runUpdateText = new Runnable() {
        @Override
        public void run() {
            // memoListに格納されたメモデータを順に表示する。
            System.out.println("BEGIN: runUpdateText");
            StringBuilder sb = new StringBuilder();
            for (Memo memo : memoList) {
                sb.append(memo.getMemoContent() + "\n");
            }
            // 生成した文字列を TextViewに表示する。
            TextView tv = findViewById(R.id.tv_list);
            tv.setText(sb.toString());
            System.out.println("END: runUpdateText");
        }
    };

    // [登録]ボタン押下イベントハンドラ
    // ・EditTextに入力された文字列を DB登録し、メモ一覧の表示を更新する。
    public void OnPushAddButton(View v) {
        // EditTextに入力された文字列を取得する。
        EditText et = findViewById(R.id.et_text);
        String txt = et.getText().toString();
        et.setText("");     // 入力欄をクリア
        System.out.println("et_text : " + txt);
        // DB更新を非UIスレッドで実行する。
        // ※ここでは Runnableを無名クラスで使う。
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("BEGIN: OnPushAddButton Thread");
                AppDatabase db = getDb(getApplicationContext());
                MemoDao dao = db.memoDao();     // DAOを生成
                Memo memo = new Memo();         // DBに書き込むデータを作成
                memo.setMemoContent(txt);       //  :
                dao.insert(memo);               // DBに書き込みを実行
                System.out.println("END: OnPushAddButton Thread");
                UpdateText();                   // UIスレッドで表示を更新
            }
        }).start();     // 上記の処理を別スレッドで実行する。
    }

    // [全消去]ボタン押下イベントハンドラ
    // DBに登録されている全メモデータを消去する。
    public void OnPushAllDeleteButton(View v) {
        // DB更新を非UIスレッドで実行する。
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("BEGIN: OnPushAllDeleteButton Thread");
                AppDatabase db = getDb(getApplicationContext());
                MemoDao dao = db.memoDao();     // DAOを生成
                dao.deleteAll();                // 全レコードを削除
                System.out.println("END: OnPushAllDeleteButton Thread");
                UpdateText();                   // UIスレッドで表示を更新
            }
        }).start();     // 上記の処理を別スレッドで実行する。
    }

    // [DB削除]ボタン押下イベントハンドラ
    // DB自体を削除する。
    public void OnPushRemoveDBButton(View v) {
        System.out.println("BEGIN: OnPushRemoveDBButton Thread");
        Context context = getApplicationContext();
        context.deleteDatabase(dbName);
        dbInst = null;
        System.out.println("END: OnPushRemoveDBButton Thread");
        UpdateText();                   // UIスレッドで表示を更新
    }
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)