Raumdatenbank migrieren

Wenn Sie Funktionen in Ihrer App hinzufügen und ändern, müssen Sie die Klassen der Room-Entität und die zugrunde liegenden Datenbanktabellen entsprechend anpassen. Es ist wichtig, Nutzerdaten zu erhalten, die sich bereits in der On-Device-Datenbank befinden, wenn durch ein App-Update das Datenbankschema geändert wird.

Room unterstützt sowohl automatische als auch manuelle Optionen für die inkrementelle Migration. Automatische Migrationen funktionieren für die meisten grundlegenden Schemaänderungen. Bei komplexeren Änderungen müssen Sie Migrationspfade möglicherweise manuell definieren.

Automatisierte Migrationen

Wenn Sie eine automatische Migration zwischen zwei Datenbankversionen deklarieren möchten, fügen Sie der Eigenschaft autoMigrations in @Database die Annotation @AutoMigration hinzu:

Kotlin

// Database class before the version update.
@Database(
  version = 1,
  entities = [User::class]
)
abstract class AppDatabase : RoomDatabase() {
  ...
}

// Database class after the version update.
@Database(
  version = 2,
  entities = [User::class],
  autoMigrations = [
    AutoMigration (from = 1, to = 2)
  ]
)
abstract class AppDatabase : RoomDatabase() {
  ...
}

Java

// Database class before the version update.
@Database(
  version = 1,
  entities = {User.class}
)
public abstract class AppDatabase extends RoomDatabase {
  ...
}

// Database class after the version update.
@Database(
  version = 2,
  entities = {User.class},
  autoMigrations = {
    @AutoMigration (from = 1, to = 2)
  }
)
public abstract class AppDatabase extends RoomDatabase {
  ...
}

Spezifikationen für die automatische Migration

Wenn Room mehrdeutige Schemaänderungen erkennt und ohne weitere Eingabe keinen Migrationsplan generieren kann, wird ein Kompilierungsfehler ausgegeben und Sie werden aufgefordert, eine AutoMigrationSpec zu implementieren. Am häufigsten tritt dies auf, wenn eine Migration eine der folgenden Aktionen umfasst:

  • Tabellen löschen oder umbenennen
  • Spalten löschen oder umbenennen

Mit AutoMigrationSpec können Sie Room die zusätzlichen Informationen zur Verfügung stellen, die es zum Generieren korrekter Migrationspfade benötigt. Definieren Sie eine statische Klasse, die AutoMigrationSpec in Ihrer RoomDatabase-Klasse implementiert, und versehen Sie sie mit einer oder mehreren der folgenden Anmerkungen:

Wenn Sie die AutoMigrationSpec-Implementierung für eine automatische Migration verwenden möchten, legen Sie die spec-Eigenschaft in der entsprechenden @AutoMigration-Anmerkung fest:

Kotlin

@Database(
  version = 2,
  entities = [User::class],
  autoMigrations = [
    AutoMigration (
      from = 1,
      to = 2,
      spec = AppDatabase.MyAutoMigration::class
    )
  ]
)
abstract class AppDatabase : RoomDatabase() {
  @RenameTable(fromTableName = "User", toTableName = "AppUser")
  class MyAutoMigration : AutoMigrationSpec
  ...
}

Java

@Database(
  version = 2,
  entities = {AppUser.class},
  autoMigrations = {
    @AutoMigration (
      from = 1,
      to = 2,
      spec = AppDatabase.MyAutoMigration.class
    )
  }
)
public abstract class AppDatabase extends RoomDatabase {
  @RenameTable(fromTableName = "User", toTableName = "AppUser")
  static class MyAutoMigration implements AutoMigrationSpec { }
  ...
}

Wenn Ihre App nach Abschluss der automatischen Migration weitere Aufgaben ausführen muss, können Sie onPostMigrate() implementieren. Wenn Sie diese Methode in Ihrem AutoMigrationSpec implementieren, wird sie von Room aufgerufen, nachdem die automatische Migration abgeschlossen ist.

Manuelle Migrationen

Wenn eine Migration komplexe Schemaänderungen umfasst, kann Room möglicherweise nicht automatisch einen geeigneten Migrationspfad generieren. Wenn Sie beispielsweise die Daten in einer Tabelle in zwei Tabellen aufteilen möchten, kann Room nicht erkennen, wie diese Aufteilung erfolgen soll. In solchen Fällen müssen Sie einen Migrationspfad manuell definieren, indem Sie eine Migration-Klasse implementieren.

Eine Migration-Klasse definiert explizit einen Migrationspfad zwischen einem startVersion und einem endVersion, indem die Methode Migration.migrate() überschrieben wird. Fügen Sie Ihre Migration-Klassen dem Datenbank-Builder mit der Methode addMigrations() hinzu:

Kotlin

val MIGRATION_1_2 = object : Migration(1, 2) {
  override fun migrate(database: SupportSQLiteDatabase) {
    database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, " +
      "PRIMARY KEY(`id`))")
  }
}

val MIGRATION_2_3 = object : Migration(2, 3) {
  override fun migrate(database: SupportSQLiteDatabase) {
    database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER")
  }
}

Room.databaseBuilder(applicationContext, MyDb::class.java, "database-name")
  .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build()

Java

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
  @Override
  public void migrate(SupportSQLiteDatabase database) {
    database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
      + "`name` TEXT, PRIMARY KEY(`id`))");
  }
};

static final Migration MIGRATION_2_3 = new Migration(2, 3) {
  @Override
  public void migrate(SupportSQLiteDatabase database) {
    database.execSQL("ALTER TABLE Book "
      + " ADD COLUMN pub_year INTEGER");
  }
};

Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
  .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();

Wenn Sie Ihre Migrationspfade definieren, können Sie für einige Versionen automatisierte Migrationen und für andere manuelle Migrationen verwenden. Wenn Sie sowohl eine automatische als auch eine manuelle Migration für dieselbe Version definieren, verwendet Room die manuelle Migration.

Migrationen testen

Migrationen sind oft komplex und eine falsch definierte Migration kann dazu führen, dass Ihre App abstürzt. Testen Sie Ihre Migrationen, um die Stabilität Ihrer App zu erhalten. Room bietet ein room-testing Maven-Artefakt, das den Testprozess sowohl für automatisierte als auch für manuelle Migrationen unterstützt. Damit dieses Artefakt funktioniert, müssen Sie zuerst das Schema Ihrer Datenbank exportieren.

Export schemas

Room kann die Schemainformationen Ihrer Datenbank zur Kompilierung in eine JSON-Datei exportieren. Die exportierten JSON-Dateien stellen den Schemaverlauf Ihrer Datenbank dar. Speichern Sie diese Dateien in Ihrem Versionskontrollsystem, damit Room zu Testzwecken niedrigere Versionen der Datenbank erstellen und die automatische Migration generieren kann.

Speicherort des Schemas mit dem Room Gradle-Plug-in festlegen

Wenn Sie Room Version 2.6.0 oder höher verwenden, können Sie das Room Gradle-Plug-in anwenden und mit der Erweiterung room das Schemaverzeichnis angeben.

Groovy

plugins {
  id 'androidx.room'
}

room {
  schemaDirectory "$projectDir/schemas"
}

Kotlin

plugins {
  id("androidx.room")
}

room {
  schemaDirectory("$projectDir/schemas")
}

Wenn sich Ihr Datenbankschema je nach Variante, Variante oder Buildtyp unterscheidet, müssen Sie verschiedene Speicherorte angeben. Verwenden Sie dazu die schemaDirectory()-Konfiguration mehrmals, wobei jedes Mal ein variantMatchName als erstes Argument verwendet wird. Jede Konfiguration kann anhand eines einfachen Vergleichs mit dem Namen der Variante mit einer oder mehreren Varianten übereinstimmen.

Achten Sie darauf, dass sie alle Varianten abdecken. Sie können auch ein schemaDirectory() ohne ein variantMatchName einfügen, um Varianten zu verarbeiten, die mit keiner der anderen Konfigurationen übereinstimmen. In einer App mit zwei Build-Varianten demo und full und zwei Build-Typen debug und release sind beispielsweise die folgenden Konfigurationen gültig:

Groovy

room {
  // Applies to 'demoDebug' only
  schemaDirectory "demoDebug", "$projectDir/schemas/demoDebug"

  // Applies to 'demoDebug' and 'demoRelease'
  schemaDirectory "demo", "$projectDir/schemas/demo"

  // Applies to 'demoDebug' and 'fullDebug'
  schemaDirectory "debug", "$projectDir/schemas/debug"

  // Applies to variants that aren't matched by other configurations.
  schemaDirectory "$projectDir/schemas"
}

Kotlin

room {
  // Applies to 'demoDebug' only
  schemaDirectory("demoDebug", "$projectDir/schemas/demoDebug")

  // Applies to 'demoDebug' and 'demoRelease'
  schemaDirectory("demo", "$projectDir/schemas/demo")

  // Applies to 'demoDebug' and 'fullDebug'
  schemaDirectory("debug", "$projectDir/schemas/debug")

  // Applies to variants that aren't matched by other configurations.
  schemaDirectory("$projectDir/schemas")
}

Speicherort des Schemas mithilfe der Option „Anmerkungs-Prozessor“ festlegen

Wenn Sie Version 2.5.2 oder niedriger von Room verwenden oder das Room Gradle-Plug-in nicht verwenden, legen Sie den Speicherort des Schemas mit der Option room.schemaLocation für den Anmerkungs-Prozessor fest.

Dateien in diesem Verzeichnis werden als Eingaben und Ausgaben für einige Gradle-Aufgaben verwendet. Für die Korrektheit und Leistung von inkrementellen und im Cache gespeicherten Builds müssen Sie Gradle über dieses Verzeichnis informieren. Verwenden Sie dazu das Gradle-Symbol CommandLineArgumentProvider.

Kopieren Sie zuerst die unten gezeigte RoomSchemaArgProvider-Klasse in die Gradle-Builddatei Ihres Moduls. Die asArguments()-Methode in der Beispielklasse gibt room.schemaLocation=${schemaDir.path} an KSP weiter. Wenn Sie KAPT und javac verwenden, ändern Sie diesen Wert stattdessen in -Aroom.schemaLocation=${schemaDir.path}.

Groovy

class RoomSchemaArgProvider implements CommandLineArgumentProvider {

  @InputDirectory
  @PathSensitive(PathSensitivity.RELATIVE)
  File schemaDir

  RoomSchemaArgProvider(File schemaDir) {
    this.schemaDir = schemaDir
  }

  @Override
  Iterable<String> asArguments() {
    // Note: If you're using KAPT and javac, change the line below to
    // return ["-Aroom.schemaLocation=${schemaDir.path}".toString()].
    return ["room.schemaLocation=${schemaDir.path}".toString()]
  }
}

Kotlin

class RoomSchemaArgProvider(
  @get:InputDirectory
  @get:PathSensitive(PathSensitivity.RELATIVE)
  val schemaDir: File
) : CommandLineArgumentProvider {

  override fun asArguments(): Iterable<String> {
    // Note: If you're using KAPT and javac, change the line below to
    // return listOf("-Aroom.schemaLocation=${schemaDir.path}").
    return listOf("room.schemaLocation=${schemaDir.path}")
  }
}

Konfigurieren Sie dann die Kompilierungsoptionen so, dass RoomSchemaArgProvider mit dem angegebenen Schemaverzeichnis verwendet wird:

Groovy

// For KSP, configure using KSP extension:
ksp {
  arg(new RoomSchemaArgProvider(new File(projectDir, "schemas")))
}

// For javac or KAPT, configure using android DSL:
android {
  ...
  defaultConfig {
    javaCompileOptions {
      annotationProcessorOptions {
        compilerArgumentProviders(
          new RoomSchemaArgProvider(new File(projectDir, "schemas"))
        )
      }
    }
  }
}

Kotlin

// For KSP, configure using KSP extension:
ksp {
  arg(RoomSchemaArgProvider(File(projectDir, "schemas")))
}

// For javac or KAPT, configure using android DSL:
android {
  ...
  defaultConfig {
    javaCompileOptions {
      annotationProcessorOptions {
        compilerArgumentProviders(
          RoomSchemaArgProvider(File(projectDir, "schemas"))
        )
      }
    }
  }
}

Einzelne Migration testen

Bevor Sie Ihre Migrationen testen können, fügen Sie das Maven-Artefakt androidx.room:room-testing aus Room zu Ihren Testabhängigkeiten hinzu und fügen Sie den Speicherort des exportierten Schemas als Asset-Ordner hinzu:

build.gradle

Groovy

android {
    ...
    sourceSets {
        // Adds exported schema location as test app assets.
        androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
    }
}

dependencies {
    ...
    androidTestImplementation "androidx.room:room-testing:2.6.1"
}

Kotlin

android {
    ...
    sourceSets {
        // Adds exported schema location as test app assets.
        getByName("androidTest").assets.srcDir("$projectDir/schemas")
    }
}

dependencies {
    ...
    testImplementation("androidx.room:room-testing:2.6.1")
}

Das Testpaket bietet die Klasse MigrationTestHelper, mit der exportierte Schemadateien gelesen werden können. Das Paket implementiert auch die JUnit4-Schnittstelle TestRule, sodass erstellte Datenbanken verwaltet werden können.

Im folgenden Beispiel wird ein Test für eine einzelne Migration veranschaulicht:

Kotlin

@RunWith(AndroidJUnit4::class)
class MigrationTest {
    private val TEST_DB = "migration-test"

    @get:Rule
    val helper: MigrationTestHelper = MigrationTestHelper(
            InstrumentationRegistry.getInstrumentation(),
            MigrationDb::class.java.canonicalName,
            FrameworkSQLiteOpenHelperFactory()
    )

    @Test
    @Throws(IOException::class)
    fun migrate1To2() {
        var db = helper.createDatabase(TEST_DB, 1).apply {
            // Database has schema version 1. Insert some data using SQL queries.
            // You can't use DAO classes because they expect the latest schema.
            execSQL(...)

            // Prepare for the next version.
            close()
        }

        // Re-open the database with version 2 and provide
        // MIGRATION_1_2 as the migration process.
        db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2)

        // MigrationTestHelper automatically verifies the schema changes,
        // but you need to validate that the data was migrated properly.
    }
}

Java

@RunWith(AndroidJUnit4.class)
public class MigrationTest {
    private static final String TEST_DB = "migration-test";

    @Rule
    public MigrationTestHelper helper;

    public MigrationTest() {
        helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
                MigrationDb.class.getCanonicalName(),
                new FrameworkSQLiteOpenHelperFactory());
    }

    @Test
    public void migrate1To2() throws IOException {
        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);

        // Database has schema version 1. Insert some data using SQL queries.
        // You can't use DAO classes because they expect the latest schema.
        db.execSQL(...);

        // Prepare for the next version.
        db.close();

        // Re-open the database with version 2 and provide
        // MIGRATION_1_2 as the migration process.
        db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);

        // MigrationTestHelper automatically verifies the schema changes,
        // but you need to validate that the data was migrated properly.
    }
}

Alle Migrationen testen

Es ist zwar möglich, eine einzelne inkrementelle Migration zu testen, wir empfehlen jedoch, einen Test durchzuführen, der alle Migrationen abdeckt, die für die Datenbank Ihrer App definiert sind. So wird sichergestellt, dass keine Abweichungen zwischen einer kürzlich erstellten Datenbankinstanz und einer älteren Instanz auftreten, die den definierten Migrationspfaden gefolgt ist.

Im folgenden Beispiel wird ein Test für alle definierten Migrationen veranschaulicht:

Kotlin

@RunWith(AndroidJUnit4::class)
class MigrationTest {
    private val TEST_DB = "migration-test"

    // Array of all migrations.
    private val ALL_MIGRATIONS = arrayOf(
            MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)

    @get:Rule
    val helper: MigrationTestHelper = MigrationTestHelper(
            InstrumentationRegistry.getInstrumentation(),
            AppDatabase::class.java.canonicalName,
            FrameworkSQLiteOpenHelperFactory()
    )

    @Test
    @Throws(IOException::class)
    fun migrateAll() {
        // Create earliest version of the database.
        helper.createDatabase(TEST_DB, 1).apply {
            close()
        }

        // Open latest version of the database. Room validates the schema
        // once all migrations execute.
        Room.databaseBuilder(
            InstrumentationRegistry.getInstrumentation().targetContext,
            AppDatabase::class.java,
            TEST_DB
        ).addMigrations(*ALL_MIGRATIONS).build().apply {
            openHelper.writableDatabase.close()
        }
    }
}

Java

@RunWith(AndroidJUnit4.class)
public class MigrationTest {
    private static final String TEST_DB = "migration-test";

    @Rule
    public MigrationTestHelper helper;

    public MigrationTest() {
        helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
                AppDatabase.class.getCanonicalName(),
                new FrameworkSQLiteOpenHelperFactory());
    }

    @Test
    public void migrateAll() throws IOException {
        // Create earliest version of the database.
        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
        db.close();

        // Open latest version of the database. Room validates the schema
        // once all migrations execute.
        AppDatabase appDb = Room.databaseBuilder(
                InstrumentationRegistry.getInstrumentation().getTargetContext(),
                AppDatabase.class,
                TEST_DB)
                .addMigrations(ALL_MIGRATIONS).build();
        appDb.getOpenHelper().getWritableDatabase();
        appDb.close();
    }

    // Array of all migrations.
    private static final Migration[] ALL_MIGRATIONS = new Migration[]{
            MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4};
}

Fehlende Migrationspfade fehlerfrei behandeln

Wenn Room keinen Migrationspfad findet, um eine vorhandene Datenbank auf einem Gerät auf die aktuelle Version zu aktualisieren, wird IllegalStateException ausgegeben. Wenn es akzeptabel ist, dass vorhandene Daten verloren gehen, wenn kein Migrationspfad vorhanden ist, rufen Sie beim Erstellen der Datenbank die Builder-Methode fallbackToDestructiveMigration() auf:

Kotlin

Room.databaseBuilder(applicationContext, MyDb::class.java, "database-name")
        .fallbackToDestructiveMigration()
        .build()

Java

Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
        .fallbackToDestructiveMigration()
        .build();

Mit dieser Methode wird Room angewiesen, die Tabellen in der Datenbank Ihrer App auf die gleiche Weise neu zu erstellen, wenn eine inkrementelle Migration durchgeführt werden muss und kein Migrationspfad definiert ist.

Wenn Sie möchten, dass Room nur in bestimmten Situationen auf die zerstörerische Rekonstruktion zurückgreift, gibt es einige Alternativen zu fallbackToDestructiveMigration():

  • Wenn bestimmte Versionen Ihres Schemaverlaufs Fehler verursachen, die Sie nicht mit Migrationspfaden beheben können, verwenden Sie stattdessen fallbackToDestructiveMigrationFrom(). Mit dieser Methode legen Sie fest, dass Room nur bei der Migration von bestimmten Versionen auf die zerstörerische Neuerstellung zurückgreifen soll.
  • Wenn Room nur bei der Migration von einer höheren Datenbankversion zu einer niedrigeren Version auf die zerstörerische Neuerstellung zurückgreifen soll, verwenden Sie stattdessen fallbackToDestructiveMigrationOnDowngrade().

Umgang mit Spaltenstandardwerten beim Upgrade auf Room 2.2.0

In Room 2.2.0 und höher können Sie mithilfe der Anmerkung @ColumnInfo(defaultValue = "...") einen Standardwert für eine Spalte definieren. In Versionen niedriger als 2.2.0 kann ein Standardwert für eine Spalte nur direkt in einer ausgeführten SQL-Anweisung definiert werden. Dadurch wird ein Standardwert erstellt, der Room nicht bekannt ist. Wenn eine Datenbank also ursprünglich mit einer Room-Version erstellt wurde, die niedriger als 2.2.0 ist, müssen Sie beim Upgrade Ihrer App auf Room 2.2.0 möglicherweise einen speziellen Migrationspfad für vorhandene Standardwerte angeben, die Sie ohne Verwendung von Room APIs definiert haben.

Angenommen, Version 1 einer Datenbank definiert eine Song-Entität:

Kotlin

// Song entity, database version 1, Room 2.1.0.
@Entity
data class Song(
    @PrimaryKey
    val id: Long,
    val title: String
)

Java

// Song entity, database version 1, Room 2.1.0.
@Entity
public class Song {
    @PrimaryKey
    final long id;
    final String title;
}

Angenommen, Version 2 derselben Datenbank enthält eine neue Spalte NOT NULL und definiert einen Migrationspfad von Version 1 zu Version 2:

Kotlin

// Song entity, database version 2, Room 2.1.0.
@Entity
data class Song(
    @PrimaryKey
    val id: Long,
    val title: String,
    val tag: String // Added in version 2.
)

// Migration from 1 to 2, Room 2.1.0.
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL(
            "ALTER TABLE Song ADD COLUMN tag TEXT NOT NULL DEFAULT ''")
    }
}

Java

// Song entity, database version 2, Room 2.1.0.
@Entity
public class Song {
    @PrimaryKey
    final long id;
    final String title;
    @NonNull
    final String tag; // Added in version 2.
}


// Migration from 1 to 2, Room 2.1.0.
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL(
            "ALTER TABLE Song ADD COLUMN tag TEXT NOT NULL DEFAULT ''");
    }
};

Dies führt in der zugrunde liegenden Tabelle zu einer Abweichung zwischen Updates und Neuinstallationen der App. Da der Standardwert für die Spalte tag nur im Migrationspfad von Version 1 zu Version 2 deklariert ist, haben Nutzer, die die App ab Version 2 installieren, keinen Standardwert für tag in ihrem Datenbankschema.

In Versionen von Room niedriger als 2.2.0 ist diese Abweichung harmlos. Wenn die App jedoch später auf Room 2.2.0 oder höher umgestellt wird und die Song-Entitätsklasse mithilfe der Anmerkung @ColumnInfo so geändert wird, dass sie einen Standardwert für tag enthält, kann Room diese Abweichung erkennen. Das führt zu fehlgeschlagenen Schemaüberprüfungen.

Damit das Datenbankschema für alle Nutzer konsistent ist, wenn Spaltenstandardwerte in Ihren früheren Migrationspfaden deklariert werden, gehen Sie beim ersten Upgrade Ihrer App auf Room 2.2.0 oder höher so vor:

  1. Deklarieren Sie Spaltenstandardwerte in den jeweiligen Entitätsklassen mit der Anmerkung @ColumnInfo.
  2. Erhöhen Sie die Datenbankversion um 1.
  3. Definieren Sie einen Migrationspfad zur neuen Version, der die Strategie zum Löschen und Neuerstellen implementiert, um den vorhandenen Spalten die erforderlichen Standardwerte hinzuzufügen.

Das folgende Beispiel veranschaulicht diesen Vorgang:

Kotlin

// Migration from 2 to 3, Room 2.2.0.
val MIGRATION_2_3 = object : Migration(2, 3) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("""
                CREATE TABLE new_Song (
                    id INTEGER PRIMARY KEY NOT NULL,
                    name TEXT,
                    tag TEXT NOT NULL DEFAULT ''
                )
                """.trimIndent())
        database.execSQL("""
                INSERT INTO new_Song (id, name, tag)
                SELECT id, name, tag FROM Song
                """.trimIndent())
        database.execSQL("DROP TABLE Song")
        database.execSQL("ALTER TABLE new_Song RENAME TO Song")
    }
}

Java

// Migration from 2 to 3, Room 2.2.0.
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("CREATE TABLE new_Song (" +
                "id INTEGER PRIMARY KEY NOT NULL," +
                "name TEXT," +
                "tag TEXT NOT NULL DEFAULT '')");
        database.execSQL("INSERT INTO new_Song (id, name, tag) " +
                "SELECT id, name, tag FROM Song");
        database.execSQL("DROP TABLE Song");
        database.execSQL("ALTER TABLE new_Song RENAME TO Song");
    }
};