Raumdatenbank migrieren

Wenn Sie Elemente in Ihrer Anwendung hinzufügen oder ändern, müssen Sie die Entitätsklassen des Typs „Raum“ und die zugrunde liegenden Datenbanktabellen anpassen, um diese Änderungen zu berücksichtigen. Es ist wichtig, Nutzerdaten, die sich bereits in der Datenbank auf dem Gerät befinden, beizubehalten, wenn ein App-Update das Datenbankschema ändert.

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

Automatisierte Migrationen

Wenn Sie eine automatisierte Migration zwischen zwei Datenbankversionen deklarieren möchten, fügen Sie dem Attribut autoMigrations in @Database eine @AutoMigration-Annotation 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 keinen Migrationsplan ohne weitere Eingaben generieren kann, wird ein Fehler bei der Kompilierungszeit ausgegeben und Sie werden aufgefordert, eine AutoMigrationSpec zu implementieren. Am häufigsten tritt dies auf, wenn eine Migration eines der folgenden Elemente umfasst:

  • Löschen oder Umbenennen einer Tabelle
  • Löschen oder Umbenennen einer Spalte

Sie können AutoMigrationSpec verwenden, um Room die zusätzlichen Informationen bereitzustellen, die zum korrekten Generieren von Migrationspfaden erforderlich sind. Definieren Sie eine statische Klasse, die AutoMigrationSpec in Ihrer RoomDatabase-Klasse implementiert, und annotieren Sie sie mit einem oder mehreren der folgenden Elemente:

Wenn Sie die AutoMigrationSpec-Implementierung für eine automatisierte Migration verwenden möchten, legen Sie das Attribut spec in der entsprechenden @AutoMigration-Annotation 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 Anwendung nach der automatisierten Migration weitere Aufgaben ausführen muss, können Sie onPostMigrate() implementieren. Wenn Sie diese Methode in Ihrem AutoMigrationSpec implementieren, wird sie nach Abschluss der automatisierten Migration von Room aufgerufen.

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, kann Room nicht erkennen, wie diese Aufteilung erfolgen soll. In solchen Fällen müssen Sie manuell einen Migrationspfad definieren. Implementieren Sie dazu eine Migration-Klasse.

Eine Migration-Klasse definiert explizit einen Migrationspfad zwischen startVersion und endVersion, indem die Methode Migration.migrate() überschrieben wird. Fügen Sie die Migration-Klassen mit der Methode addMigrations() zu Ihrem Datenbank-Builder 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 automatisierte Migration als auch eine manuelle Migration für dieselbe Version definieren, verwendet Room die manuelle Migration.

Migrationen testen

Migrationen sind häufig komplex und eine falsch definierte Migration kann zum Absturz Ihrer Anwendung führen. Testen Sie Ihre Migrationen, um die Stabilität Ihrer Anwendung zu wahren. Room bietet ein room-testing-Maven-Artefakt, das den Testprozess für automatisierte und manuelle Migrationen unterstützt. Damit dieses Artefakt funktioniert, müssen Sie zuerst das Schema Ihrer Datenbank exportieren.

Schemas exportieren

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

Schemaort mit Room Gradle-Plug-in festlegen

Wenn Sie die 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.

Cool

plugins {
  id 'androidx.room'
}

room {
  schemaDirectory "$projectDir/schemas"
}

Kotlin

plugins {
  id("androidx.room")
}

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

Wenn sich Ihr Datenbankschema je nach Variante, Flavor oder Build-Typ unterscheidet, müssen Sie verschiedene Speicherorte angeben. Dazu verwenden Sie die schemaDirectory()-Konfiguration mehrmals mit jeweils einem variantMatchName als erstem Argument. Bei jeder Konfiguration können auf Basis eines einfachen Vergleichs mit dem Variantennamen eine oder mehrere Varianten zugeordnet werden.

Achten Sie darauf, dass diese Informationen vollständig sind und alle Varianten abdecken. Sie können auch eine schemaDirectory() ohne variantMatchName einfügen, um Varianten zu verarbeiten, die keiner der anderen Konfigurationen entsprechen. In einer Anwendung mit den beiden Build-Varianten demo und full sowie den Build-Typen debug und release sind beispielsweise folgende Konfigurationen gültig:

Cool

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

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

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

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

Kotlin

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

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

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

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

Schemastandort mit Option für Annotationsprozessor festlegen

Wenn Sie die Version 2.5.2 oder niedriger von Room oder das Room Gradle-Plug-in nicht nutzen, legen Sie den Speicherort des Schemas mit der Annotationsprozessoroption room.schemaLocation fest.

Dateien in diesem Verzeichnis werden für einige Gradle-Aufgaben als Ein- und Ausgaben verwendet. Damit inkrementelle und im Cache gespeicherte Builds korrekt und leistungsfähig sind, müssen Sie Gradle mit dem CommandLineArgumentProvider von Gradle über dieses Verzeichnis informieren.

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

Cool

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:

Cool

// 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 die Migrationen testen können, fügen Sie den Testabhängigkeiten das Maven-Artefakt androidx.room:room-testing aus Room hinzu und fügen Sie den Speicherort des exportierten Schemas als Asset-Ordner hinzu:

build.gradle-Datei

Cool

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 eine MigrationTestHelper-Klasse, die exportierte Schemadateien lesen kann. Das Paket implementiert auch die JUnit4-Schnittstelle TestRule, sodass es erstellte Datenbanken verwalten kann.

Das folgende Beispiel zeigt einen Test für eine einzelne Migration:

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 hinzuzufügen, der alle für die Datenbank Ihrer Anwendung definierten Migrationen abdeckt. Dadurch wird sichergestellt, dass es keine Diskrepanz zwischen einer kürzlich erstellten Datenbankinstanz und einer älteren Instanz gibt, die den definierten Migrationspfaden gefolgt ist.

Das folgende Beispiel zeigt einen Test für alle definierten Migrationen:

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};
}

Ordnungsgemäße Handhabung fehlender Migrationspfade

Wenn Room keinen Migrationspfad zum Upgrade einer vorhandenen Datenbank auf einem Gerät auf die aktuelle Version findet, tritt ein IllegalStateException auf. Wenn es akzeptabel ist, vorhandene Daten zu verlieren, wenn ein Migrationspfad fehlt, 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 Anwendung destruktiv neu zu erstellen, wenn eine inkrementelle Migration erforderlich ist und kein Migrationspfad definiert ist.

Wenn Sie möchten, dass in „Room“ nur in bestimmten Situationen auf die destruktive Neuerstellung zurückgegriffen werden soll, gibt es einige Alternativen zu fallbackToDestructiveMigration():

  • Wenn bestimmte Versionen Ihres Schemaverlaufs Fehler verursachen, die sich nicht mit Migrationspfaden beheben lassen, verwenden Sie stattdessen fallbackToDestructiveMigrationFrom(). Diese Methode gibt an, dass Room nur bei der Migration von bestimmten Versionen auf die destruktive Neuerstellung zurückgreifen soll.
  • Wenn Room bei der Migration von einer höheren Datenbankversion zu einer niedrigeren Version nur auf die destruktive Neuerstellung zurückgreifen soll, verwenden Sie stattdessen fallbackToDestructiveMigrationOnDowngrade().

Standardspaltenwerte beim Upgrade auf Raum 2.2.0 verarbeiten

In Raum 2.2.0 und höher können Sie mit der Annotation @ColumnInfo(defaultValue = "...") einen Standardwert für eine Spalte definieren. In Versionen vor 2.2.0 können Sie einen Standardwert für eine Spalte nur definieren, wenn Sie ihn direkt in einer ausgeführten SQL-Anweisung definieren. Dadurch wird ein Standardwert erstellt, den Room nicht bekannt ist. Wenn also eine Datenbank ursprünglich mit einer Version von Room erstellt wurde, die älter als Version 2.2.0 ist, müssen Sie für ein Upgrade Ihrer Anwendung 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 fügt eine neue Spalte NOT NULL hinzu 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 zu einer Abweichung in der zugrunde liegenden Tabelle zwischen Updates und Neuinstallationen der Anwendung. Da der Standardwert für die Spalte tag nur im Migrationspfad von Version 1 zu Version 2 deklariert ist, haben Nutzer, die die Anwendung ab Version 2 installieren, nicht den Standardwert für tag in ihrem Datenbankschema.

In den Versionen von Room (unter 2.2.0) sind diese Abweichungen harmlos. Wenn die Anwendung jedoch später auf Raum 2.2.0 oder höher aktualisiert und die Entitätsklasse Song so geändert wird, dass sie mithilfe der Annotation @ColumnInfo einen Standardwert für tag enthält, kann der Raum diese Abweichung erkennen. Dies führt zu fehlgeschlagenen Schemavalidierungen.

Damit das Datenbankschema für alle Nutzer konsistent ist, wenn in Ihren früheren Migrationspfaden Spaltenstandardwerte deklariert werden, führen Sie beim ersten Upgrade Ihrer Anwendung für Raum 2.2.0 oder höher die folgenden Schritte aus:

  1. Geben Sie mithilfe der Annotation @ColumnInfo die Standardwerte von Spalten in ihren jeweiligen Entitätsklassen an.
  2. Erhöhen Sie die Versionsnummer der Datenbank um 1.
  3. Legen Sie einen Migrationspfad zur neuen Version fest, in der die Strategie zum Löschen und Neuerstellen implementiert ist, 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");
    }
};