Room Database:データベース(識別子)のOpenとClose

投稿日:  更新日:

Room(Android Jetpack)でデータベースを構築した場合、データベースへアクセスする際に、データベースのOpenとCloseという処理を伴います。

この点について、まとめました。

※環境:Android Studio Ladybug | 2024.2.1 Patch 3
    Kotlin 2.0.0
    androidx.sqlite:sqlite:2.4.0
    androidx.room:room-*:2.6.1

※サンプルの全体像は「Roomでデータベースを構築」を参照

スポンサーリンク

データベースのOpen

データベースのアクセスを開始する前に、データベースと接続の確立を行います。

この接続の確立を行う処理が「データベース(識別子)のOpen」に当たります。

データベース識別子は、その結果として得られるものです。

Room.databaseBuilder#build( )の実行は実装済みのRoomDatabaseインスタンス(識別子)を返します。ちなみに、RoomDatabaseは抽象関数(abstract)です。

この時、内部でデータベースのOpenが行われています。

@Database(entities = arrayOf(Player::class), version = 1)
abstract class GameDb : RoomDatabase() {
 
    abstract fun playerDao(): PlayerDao
 
    companion object {
        @Volatile
        private var instance: GameDb? = null
        fun getDatabase(context: Context): GameDb {
            return instance ?: synchronized(this) {
                val _instans = Room.databaseBuilder(
                    context,
                    GameDb::class.java, // 自身のクラス
                    "Game.db"           // データベース名
                )
                    .build()
                instance = _instans
                _instans
            }
        }
    }
}

ただし、RoomのデータベースのOpenは、ユーザ側から見えている部分に過ぎません。

RoomデータベースのOpen

RoomはSQLite APIのラッパーAPIであって、内部でSQLite APIを呼び出しています。

つまり、マネージメントシステムとやり取り(問い合わせ⇔返答)を行っているのは、SQLite APIです。

ですので、内部側でSQLite APIデータベースのOpenが行われています。

RoomとSQLite APIデータベースのOpenのタイミングは異なります(黄色のイベント)。注意が必要です。

スポンサーリンク

データベースのClose

データベースの為に確保されたリソース(主にメモリー)を開放します。

Closeは関数RoomDatabase#close( )で行います。

    @Suppress("Deprecation") // Due to usage of `mDatabase`
    open val isOpen: Boolean
        get() = (autoCloser?.isActive ?: mDatabase?.isOpen) == true

    ...

    /**
     * Closes the database if it is already open.
     */
    open fun close() {
        if (isOpen) {
            val closeLock: Lock = readWriteLock.writeLock()
            closeLock.lock()
            try {
                invalidationTracker.stopMultiInstanceInvalidation()
                openHelper.close()		// ⇐ SQLiteOpenHelper#close( )が呼ばれる
            } finally {
                closeLock.unlock()
            }
        }
    }

※mDatabase:SupportSQLiteDatabaseのインスタンス
※openHelper:SupportSQLiteOpenHelperのインスタンス

SuppoerXXXはRoomからSQLite APIへ関数の実行(ここではclose)を橋渡しするクラスです。

ですので、SQLite APIのデータベースのCloseと同様に、SQLiteOpenHelper#close( )が呼ばれることになります。
※詳細は「SQLite API Database:データベース(識別子)のOpenとClose」を参照

RoomデータベースクのClose

CloseされるのはSQLite APIの識別子であって、Roomの識別子はCloseされません。

スポンサーリンク

注意点1:Close後の識別子は使用不可

Close後の識別子は使用できません。使用した場合はエラーになり、アプリが落ちます。

            var _gameDb = GameDb.getDatabase(this@MainActivity)
            Log.i(TAG, "RoomDatabase(D-0) = ${_gameDb.hashCode()}, isOpen:${_gameDb.isOpen}")
            _gameDb.playerDao().insert(Player(0, "RRRRRRRR", 123))
            Log.i(TAG, "RoomDatabase(D-1) = ${_gameDb.hashCode()}, isOpen:${_gameDb.isOpen}")
            _gameDb.close()
            Log.i(TAG, "RoomDatabase(D-2) = ${_gameDb.hashCode()}, isOpen:${_gameDb.isOpen}")

            _gameDb = GameDb.getDatabase(this@MainActivity)
            Log.i(TAG, "RoomDatabase(D-3) = ${_gameDb.hashCode()}, isOpen:${_gameDb.isOpen}")
            _gameDb.playerDao().insert(Player(0, "SSSSSSSS", 456))
            Log.i(TAG, "RoomDatabase(D-4) = ${_gameDb.hashCode()}, isOpen:${_gameDb.isOpen}")
RoomDatabase(D-0) = 22493922, isOpen:false
RoomDatabase(D-1) = 22493922, isOpen:true   // アクセス時にSQLite APIの識別子がOpenされる
RoomDatabase(D-2) = 22493922, isOpen:false  // SQLite APIの識別子がCloseされる
RoomDatabase(D-3) = 22493922, isOpen:false
...
java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
...
スポンサーリンク

注意点2:Close後の識別子を再Open

Close後の識別子を再Openするには、Roomの識別子を再作成するしかありません。

この為に、シングルトン化で行われた自己保持(instance変数)を解除する処理が必要になります。
※シングルトン化については「Room Database:識別子(RoomDatabase)の共有」を参照

@Database(entities = arrayOf(Player::class), version = 1)
abstract class GameDb : RoomDatabase() {

    abstract fun playerDao(): PlayerDao

    companion object {
        @Volatile
        private var instance: GameDb? = null
        fun getDatabase(context: Context): GameDb {
            return instance ?: synchronized(this) {
                val _instans = Room.databaseBuilder(
                    context,
                    GameDb::class.java,
                    "Game.db"
                )
                    .build()
                instance = _instans
                _instans
            }
        }

        fun closeDatabase() {
            instance?.close()
            instance = null
        }
    }
}
            var _gameDb = GameDb.getDatabase(this@MainActivity)
            Log.i(TAG, "RoomDatabase(D-0) = ${_gameDb.hashCode()}, isOpen:${_gameDb.isOpen}")
            _gameDb.playerDao().insert(Player(0, "RRRRRRRR", 123))
            Log.i(TAG, "RoomDatabase(D-1) = ${_gameDb.hashCode()}, isOpen:${_gameDb.isOpen}")
            GameDb.closeDatabase()
            Log.i(TAG, "RoomDatabase(D-2) = ${_gameDb.hashCode()}, isOpen:${_gameDb.isOpen}")

            _gameDb = GameDb.getDatabase(this@MainActivity)
            Log.i(TAG, "RoomDatabase(D-3) = ${_gameDb.hashCode()}, isOpen:${_gameDb.isOpen}")
            _gameDb.playerDao().insert(Player(0, "SSSSSSSS", 456))
            Log.i(TAG, "RoomDatabase(D-4) = ${_gameDb.hashCode()}, isOpen:${_gameDb.isOpen}")
RoomDatabase(D-0) = 18034992, isOpen:false
RoomDatabase(D-1) = 18034992, isOpen:true
RoomDatabase(D-2) = 18034992, isOpen:false
RoomDatabase(D-3) = 26510877, isOpen:false  // Roomの識別子の再作成
RoomDatabase(D-4) = 26510877, isOpen:true   // アクセス時にSQLite APIの識別子がOpenされる

ただし、Openは多くのコスト(処理時間、CPUの能力消費、メモリー消費)を必要とします。

小まめなCloseとOpenの繰り返しは、アプリケーション全体のパフォーマンス低下を引き起こす可能性があるので、行わない実装にすべきです。

Closeのタイミング

識別子のCloseは、問題点1,2を引き起こす可能性があります。エラー時は「アプリが落ちる」という、非常に深刻な状況です。

ですので、アプリケーションの起動している間はCloseを行いません。

明示的にCloseを行わなかったとしても、アプリケーションのプロセスが終了する時に、全てのリソースは開放されます。その開放に任せれば良いです。

あえて明示的に行いたいのであれば、アプリケーションコンポーネントのトップに当たるMainActivity#onDestroyで行います。アプリケーションが終了に達していると、考えられるためです。

識別子をオープンしておく範囲

class MainActivity : ComponentActivity() {

    private lateinit var gameDb: GameDb
    private lateinit var playerFlow: MutableStateFlow<List<Player>>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
		...
      
	    gameDb = (application as MyApplication).gameDb
        playerFlow = MutableStateFlow<List<Player>>(listOf<Player>())
    }

    override fun onDestroy() {
        super.onDestroy()
        gameDb.close()
    }
}
スポンサーリンク

関連記事:

Androidアーキテクチャコンポーネント(AAC)は「堅牢でテストとメンテナンスが簡単なアプリの設計を支援する」とドキュメントで説明されています。 有効そうだけど、実態がよくわからないので、いろいろ調べて理解した内容をまとめました。 ...
Androidは標準でSQLiteというRDBMS(Relational Database Management System)を扱います。 SQLiteは少し個性的なデータベースです。 データベース全般の説明を通して、SQLiteと他の違いをまとめます。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0 ...
データベースを扱うアプリケーションの開発で、動作を確認するためにデータベースの閲覧が出来ると便利です。 データベースは、マネージメントシステムを介してアクセスされるため、プログラムからブラックボックスに見えます。 ですので、データベース内部の問題は見つけ難いです。 デバックおいてデータベースの閲覧が出来れば、内部の問題を用意に確認できます。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     DB Browser for SQLite バージョン 3.13.1 ...
Androidが標準で扱うデータベースはSQLiteです。 ※詳細は「Androidで扱うデータベース」を参照 データベースを構築する方法は「SQLite API(Android SDK)」と「Room(Android Jetpack)」の2通りがあります。 今回は「Room」でデータベースを構築する方法を紹介します。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0     androidx.sqlite:sqlite:2.4.0     androidx.room:room-*:2.6.1 ...
アプリケーションが始めて起動した直後に、データの事前取り込み(初期状態の設定)を必要とする場合があります。 事前取り込みの方法ついて、まとめました。 Room(Android Jetpack)でデータベースを構築した場合です。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 3     Kotlin 2.0.0     androidx.sqlite:sqlite:2.4.0     androidx.room:room-*:2.6.1     DB Browser for SQLite バージョン 3.13.1 ※サンプルの全体像は「Roomでデータベースを構築」を参照 ...
Roomは処理の開始時にコールバックを受け取ることができます。 このコールバックついて、まとめました。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 3     Kotlin 2.0.0     androidx.sqlite:sqlite:2.4.0     androidx.room:room-*:2.6.1     DB Browser for SQLite バージョン 3.13.1 ※サンプルの全体像は「Roomでデータベースを構築」を参照 ...
Room(Android Jetpack)でデータベースを構築した場合、アプリのパフォーマンスを考えると、識別子の共有が必要になってきます。 この点について、まとめました。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 3     Kotlin 2.0.0     androidx.sqlite:sqlite:2.4.0     androidx.room:room-*:2.6.1 ※サンプルの全体像は「Roomでデータベースを構築」を参照 ...
スポンサーリンク