LoginSignup
3
3

More than 3 years have passed since last update.

Roomデータベースファイルのエクスポート・インポートを行う雑な方法

Posted at

Roomで管理するSQLiteのデータベースをエクスポートまたはインポートしたいときに、CSVの入出力を行うのが面倒なので、データベースファイルをそのままコピーするという方法を見つけた。

ほとんど何もバリデーションを行わないため、実際にリリースするアプリには怖くて使えないが、自分用のソフトウェアには問題なく利用できる。

実際に、RoomでデータベースをコピーするためのAPIがあれば良いのだが、簡単に動くものはなさそうだ。

エクスポート

データベースを現在の状態で外部のURIにエキスポートしたい。

元のデータベースファイルの取得

ApplicationContextがあれば、

File sourceFile = getApplicationContext().getDatabasePath(DATABASE_NAME);

でdbファイルを取得できる。このときのDATABASE_NAMEはRoomDatabase.Builderでビルドするときに使った名前。

WALのチェックポイント

RoomではWrite-Ahead Logging (WAL)というものを採用している。これは、ジャーナルファイルに全てのトランズアクションを記録し、ジャーナルファイルが一定のサイズを超えると元のデータベースファイルが更新される仕組みである。よって、エクスポートするときにデータベースファイルを最新の状態にしておく必要がある。
チェックポイントを以下の構文で作動させることで、データベースファイルの内容を手動で更新できる。

pragma wal_checkpoint(full)

fullの代わりに、passive、restart、truncateのオプションがある。詳しくはドキュメントを参照。

Javaでは色々な書き方があるがこれだけのためにDaoを作りたくなかったので、ダイレクトに実行した。(mDbRoomDatabaseのインスタンス)

SupportSQLiteDatabase db = mDb.getOpenHelper().getWritableDatabase();
try {
    Cursor c = db.query(new SimpleSQLiteQuery("pragma wal_checkpoint(full)"));
    if (c.moveToFirst() && c.getInt(0) == 1) 
        throw new RuntimeException("チェックポイントが正常に完了しませんでした。");
    db.close();
} catch (Exception e) {
    // エラー対処
}

参照:https://stackoverflow.com/a/52089040/9121127

データのコピー

以下のようにしてファイル間でデータを移した。Fileであればnew FileInputStream(file)またはnew FileOutputStream(file)、UriであればgetContentResolver().openInputStream(uri)またはgetContentResolver().openOutputStream(uri)を使う。

try {
    FileInputStream is = new FileInputStream(sourceFile);
    FileOutputStream os = (FileOutputStream)getContentResolver().openOutputStream(destinationUri);
    FileChannel src = is.getChannel();
    FileChannel dst = os.getChannel();

    dst.transferFrom(src, 0, src.size());

    src.close();
    dst.close();
} catch (IOException e) {
    // エラー処理
}

インポート

上のステップでエキスポートしたデータベースファイルを読み込みたい。明らかに、他のファイルをそのまま読み込むと危険なので注意する必要がある。

手順はほとんど同じだが、コピーをする前に既存のインスタンスへのコネクションを閉じる必要がある。

mDb.close();
File destinationFile = getApplicationContext().getDatabasePath(DATABASE_NAME);

try {
    FileOutputStream os = new FileOutputStream(destinationFile);
    FileInputStream is = (FileInputStream)getContentResolver().openInputStream(sourceUri);
    FileChannel src = is.getChannel();
    FileChannel dst = os.getChannel();

    dst.transferFrom(src, 0, src.size());

    src.close();
    dst.close();
} catch (IOException e) {
    // エラー処理
}

データベースを更新した後は、アクティビティをリスタートした。

Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

createFromAsset

RoomにcreateFromAssetやcreateFromFileというAPIがあるが、使い方がドキュメントを読んだだけでは分からなかった。
本当は公式APIの方が安全なのだが、残念ながら動かすことはできずに終わった。

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