新しいテーブルのみが追加された場合のルームデータベースの移行


99

仮定してみましょう、私は単純な部屋データベースを持っています:

@Database(entities = {User.class}, version = 1)
abstract class AppDatabase extends RoomDatabase {
    public abstract Dao getDao();
}

ここで、新しいエンティティを追加します。Petバージョンを2にバンプします。

@Database(entities = {User.class, Pet.class}, version = 2)
abstract class AppDatabase extends RoomDatabase {
    public abstract Dao getDao();
}

もちろん、Roomは例外をスローします。 java.lang.IllegalStateException: A migration from 1 to 2 is necessary.

私はUserクラスを変更していないと仮定し(すべてのデータが安全であるため)、新しいテーブルを作成するだけの移行を提供する必要があります。そこで、Roomによって生成されたクラスを調べ、生成されたクエリを検索して新しいテーブルを作成し、それをコピーして移行に貼り付けます。

final Migration MIGRATION_1_2 =
        new Migration(1, 2) {
            @Override
            public void migrate(@NonNull final SupportSQLiteDatabase database) {
                database.execSQL("CREATE TABLE IF NOT EXISTS `Pet` (`name` TEXT NOT NULL, PRIMARY KEY(`name`))");
            }
        };

ただし、手動で行うのは不便です。部屋に伝える方法はありますか既存のテーブルには触れないので、データは安全です。移行を作成してください。


これに対する解決策を見つけましたか?
Mikkel Larsen

3
私は同じ問題を抱えており、あなたが行ったのと同じ方法で修正しましたが、解決策も見つかりませんでした。その時私が一人じゃないのは嬉しい。:)
Mikkel Larsen、

3
こっちも一緒。私はそれが非常にその部屋がdatabase_implのクエリ内部を作成生成することができる不便を感じるが、ちょうど移行開始されると......、テーブルを作成することはできません
JacksOnF1re

1
私はそのような機能に多くを与えます...移行とフォールバックメカニズムを混合するのもいいでしょう...
Appyx

3
これが役立つかどうかはわかりませんが、RoomにはデータベーススキーマをJSONファイルにエクスポートするオプションがあります。developer.android.com/training/data-storage/room/…明らかに、これはまだ手動で移行スクリプトを追加することを意味しますが、SQLステートメントを取得するために自動生成されたクラスをルーティングする必要はありません。
James Lendrem

回答:


76

部屋には、少なくともまでは、適切な移行システムがありませ2.1.0-alpha03

したがって、より良い移行システムができるまでは、会議室で簡単に移行できるようにいくつかの回避策があります。

@Database(createNewTables = true) またはのようなメソッドは存在しないためMigrationSystem.createTable(User::class)、実行できるのは唯一の可能な方法です

CREATE TABLE IF NOT EXISTS `User` (`id` INTEGER, PRIMARY KEY(`id`))

migrateメソッド内。

val MIGRATION_1_2 = object : Migration(1, 2){
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("CREATE TABLE IF NOT EXISTS `User` (`id` INTEGER, PRIMARY KEY(`id`))")
    }
}

上記のSQLスクリプトを取得するには、4つの方法があります

1.自分で書く

基本的には、Roomが生成するスクリプトと一致する上記のスクリプトを記述する必要があります。この方法は可能ですが、実現不可能です。(50のフィールドがあることを考慮してください)

2.スキーマのエクスポート

アノテーションexportSchema = true内に含めると@Database、Roomはプロジェクトフォルダーの/ schemas内にデータベーススキーマを生成します。使い方は

@Database(entities = [User::class], version = 2, exportSchema = true)
abstract class AppDatabase : RoomDatabase {
   //...
}

build.gradeアプリモジュールの以下の行が含まれていることを確認してください

kapt {
    arguments {
        arg("room.schemaLocation", "$projectDir/schemas".toString())
    }
} 

プロジェクトを実行またはビルドすると、JSONファイルが取得されます2.json。このファイルには、Roomデータベース内のすべてのクエリが含まれています。

  "formatVersion": 1,
  "database": {
    "version": 2,
    "identityHash": "325bd539353db508c5248423a1c88c03",
    "entities": [
      {
        "tableName": "User",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, PRIMARY KEY(`id`))",
        "fields": [
          {
            "fieldPath": "id",
            "columnName": "id",
            "affinity": "INTEGER",
            "notNull": true
          },

したがって、メソッドcreateSql内に上記を含めることができますmigrate

3. AppDatabase_Implからクエリを取得する

スキーマをエクスポートしたくない場合でも、AppDatabase_Impl.javaファイルを生成するプロジェクトを実行またはビルドすることで、クエリを取得できます。そして、あなたが持つことができる指定されたファイル内で。

@Override
public void createAllTables(SupportSQLiteDatabase _db) {
  _db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`id` INTEGER, PRIMARY KEY(`id`))");

createAllTablesメソッド内には、すべてのエンティティの作成スクリプトがあります。それを取得して、migrateメソッド内に含めることができます。

4.注釈処理。

お察しの通り、部屋は、上記のすべてを生成しschema、そしてAppDatabase_Implあなたが追加注釈処理、コンパイル時間内とを持つファイル

kapt "androidx.room:room-compiler:$room_version"

つまり、同じことを行って、必要なすべての作成クエリを生成する独自の注釈処理ライブラリを作成することもできます。

アイデアは、@EntityとのRoomアノテーション用のアノテーション処理ライブラリを作成すること@Databaseです。@Entityたとえば、注釈が付けられたクラスを考えてみましょう。これらはあなたが従わなければならないステップです

  1. 新しいものを作成しStringBuilder、「存在しない場合はテーブルを作成」​​を追加
  2. から、class.simplenameまたはのtableNameフィールドでテーブル名を取得します@Entity。それをあなたに追加StringBuilder
  3. 次に、クラスのフィールドごとにSQLの列を作成します。フィールド自体または@ColumnInfo注釈のいずれかによる、フィールドの名前、タイプ、null可能性を取得します。すべてのフィールドについてid INTEGER NOT NULL、列のスタイルをに追加する必要がありますStringBuilder
  4. 主キーを追加 @PrimaryKey
  5. 追加ForeignKeyIndices、存在する場合。
  6. 文字列に変換した後、使用したい新しいクラスに保存します。たとえば、以下のように保存します
public final class UserSqlUtils {
  public String createTable = "CREATE TABLE IF NOT EXISTS User (id INTEGER, PRIMARY KEY(id))";
}

その後、次のように使用できます

val MIGRATION_1_2 = object : Migration(1, 2){
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL(UserSqlUtils().createTable)
    }
}

自分でチェックできるライブラリを自分で作成し、プロジェクトで使用することもできます。私が作成したライブラリは完全ではなく、テーブル作成の要件を満たしていることに注意してください。

より良い移行のためのRoomExtension

RoomExtensionを使用するアプリケーション

お役に立てば幸いです。

更新

この回答を書いている時点で、ルームバージョンはで2.1.0-alpha03あり、開発者にメールを送信したときに、

より良い移行システムが期待されます 2.2.0

残念ながら、まだより良い移行システムがありません。


3
Room 2.2.xの方が移行が改善されることを、どこで読んだか教えていただけますか?私はその主張をするものを見つけることができません、そして私たちは現在2.1.0ベータに取り組んでいるので、2.2.0で何が今のところ不明であるようです。
jkane001

1
@ jkane001部屋の開発者の1人にメールを送ったところ、返信がありました。2.2.xのについてはそのような公告は(まだ?)はありません
musooff

4
現在、バージョン2.2.2とのまだ良く移行:(ただし、これは優れた答えは、そのための1ので、私に仕事のトンを保存。
smitty1

@androiddeveloper#4を除くすべて(注釈処理)
musooff

1
@musooffテーブル作成を追加しても大丈夫だと思います。これは、「createAllTables」関数からコードをコピーする最も安全な方法です。
Android開発者

3

Roomでは、データを失うことなくテーブルを自動作成することはできません。

マイグレーションの記述は必須です。それ以外の場合は、すべてのデータが消去され、新しいテーブル構造が作成されます。


0

あなたはこれを行うことができます

@Database(entities = {User.class, Pet.class}, version = 2)

abstract class AppDatabase extends RoomDatabase {
public abstract Dao getDao();
public abstract Dao getPetDao();
}

残りはあなたが前述したのと同じになります-

 db = Room.databaseBuilder(this, AppDatabase::class.java, "your_db")
        .addMigrations(MIGRATION_1_2).build()

リファレンス- 詳細


0

次のgradleコマンドをapp.gradleのdefaultConfigに追加できます。

javaCompileOptions {
        annotationProcessorOptions {
            arguments = ["room.schemaLocation":
                                 "$projectDir/schemas".toString()]
        }
    }

これを実行すると、関連するCREATE TABLEステートメントを含むテーブル名のリストがコンパイルされ、そこから移行オブジェクトにコピーして貼り付けることができます。テーブル名を変更する必要がある場合があります。

たとえば、これは私の生成されたスキーマからのものです:

"tableName": "assets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `base` TEXT NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`asset_id`))"

そして、createSqlステートメントをコピーして貼り付け、 '$ {TABLE_NAME}'をテーブル名を 'assets'に変更して、自動生成されたRoom createステートメントを作成します。


-1

この場合、移行を行う必要はありません。データベースインスタンスの作成時に.fallbackToDestructiveMigration()を呼び出すことができます。

例:

    instance = Room.databaseBuilder(context, AppDatabase.class, "database name").fallbackToDestructiveMigration().build();

そして、データベースのバージョンを変更することを忘れないでください。


このソリューションは、既存のテーブルからすべてのデータを削除します。
Piotr Aleksander Chmielowski

残念ながらそうです。「このメソッドを呼び出して、この動作を変更して、クラッシュする代わりにデータベースを再作成することができます。これにより、Roomが管理するデータベーステーブル内のすべてのデータが削除されます。」
rudicjovan 2018

-2

多分この場合(他のものを変更せずに新しいテーブルのみを作成した場合)、これはマイグレーションをまったく作成せずに行うことができますか?


1
いいえ、この場合、部屋はログをスローします:java.lang.IllegalStateException:{old_version}から{new_version}への移行が必要です
Max Makeichik
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.