Room veritabanınızı taşıyın

Uygulamanıza özellik ekleyip değiştirirken, Room varlık sınıflarınızı ve temel veritabanı tablolarınızı bu değişiklikleri yansıtacak şekilde değiştirmeniz gerekir. Bir uygulama güncellemesinde veritabanı şeması değiştiğinde zaten cihaz üzerindeki veritabanında bulunan kullanıcı verilerinin korunması önemlidir.

Oda, artımlı taşıma için hem otomatik hem de manuel seçenekleri destekler. Otomatik taşıma işlemleri, çoğu temel şema değişikliği için işe yarar ancak daha karmaşık değişiklikler için taşıma yollarını manuel olarak tanımlamanız gerekebilir.

Otomatik taşıma işlemleri

İki veritabanı sürümü arasında otomatik taşıma işlemi bildirmek için @Database içindeki autoMigrations mülküne bir @AutoMigration ek açıklaması ekleyin:

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 {
  ...
}

Otomatik taşıma özellikleri

Room, belirsiz şema değişiklikleri algılarsa ve daha fazla giriş olmadan bir taşıma planı oluşturamazsa derleme zamanı hatası verir ve sizden bir AutoMigrationSpec uygulamanızı ister. Bu durum en yaygın olarak, taşıma işlemi aşağıdakilerden birini içerdiğinde ortaya çıkar:

  • Tablo silme veya yeniden adlandırma.
  • Bir sütunu silme veya yeniden adlandırma.

Odaya taşıma yollarını doğru şekilde oluşturmak için gereken ek bilgileri vermek için AutoMigrationSpec kullanabilirsiniz. RoomDatabase sınıfınızda AutoMigrationSpec uygulamasını uygulayan bir statik sınıf tanımlayın ve buna aşağıdakilerden biri veya birkaçı ile ek açıklama ekleyin:

Otomatik taşıma işleminde AutoMigrationSpec uygulamasını kullanmak için ilgili @AutoMigration ek açıklamasında spec özelliğini ayarlayın:

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 { }
  ...
}

Otomatik taşıma işlemi tamamlandıktan sonra uygulamanızın daha fazla işlem yapması gerekirse onPostMigrate() aracını uygulayabilirsiniz. Bu yöntemi AutoMigrationSpec uygulamanıza uygularsanız Room, otomatik taşıma tamamlandıktan sonra bu yöntemi çağırır.

Manuel taşıma işlemleri

Taşıma işleminin karmaşık şema değişiklikleri içerdiği durumlarda Room, otomatik olarak uygun bir taşıma yolu oluşturamayabilir. Örneğin, bir tablodaki verileri iki tabloya bölmeye karar verirseniz Oda bu bölme işlemini nasıl gerçekleştireceğini söyleyemez. Bu gibi durumlarda, Migration sınıfı uygulayarak taşıma yolunu manuel olarak tanımlamanız gerekir.

Migration sınıfı, Migration.migrate() yöntemini geçersiz kılarak startVersion ile endVersion arasındaki taşıma yolunu açıkça tanımlar. addMigrations() yöntemini kullanarak Migration sınıflarınızı veritabanı oluşturucunuza ekleyin:

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();

Taşıma yollarınızı tanımlarken bazı sürümler için otomatik taşımaları, bazıları için de manuel taşımaları kullanabilirsiniz. Aynı sürüm için hem otomatik taşıma hem de manuel taşıma tanımlarsanız Room manuel taşımayı kullanır.

Taşımaları test etme

Taşıma işlemleri genellikle karmaşıktır ve yanlış tanımlanmış bir taşıma işlemi, uygulamanızın kilitlenmesine neden olabilir. Uygulamanızın kararlılığını korumak için taşıma işlemlerinizi test edin. Room, hem otomatik hem de manuel taşıma işlemlerinde test sürecine yardımcı olmak için bir room-testing Maven yapısı sağlar. Bu yapının çalışması için önce veritabanınızın şemasını dışa aktarmanız gerekir.

Şemaları dışa aktarma

Oda, derleme sırasında veritabanınızın şema bilgilerini bir JSON dosyasına aktarabilir. Dışa aktarılan JSON dosyaları, veritabanınızın şema geçmişini temsil eder. Bu dosyaları sürüm kontrol sisteminizde depolayın. Böylece Room, test amacıyla ve otomatik taşıma oluşturmayı etkinleştirmek için veritabanının eski sürümlerini oluşturabilir.

Room Gradle Eklentisi'ni kullanarak şema konumunu ayarlayın

Room sürüm 2.6.0 veya sonraki bir sürümü kullanıyorsanız şema dizinini belirtmek için Room Gradle Eklentisi'ni ve room uzantısını kullanabilirsiniz.

Modern

plugins {
  id 'androidx.room'
}

room {
  schemaDirectory "$projectDir/schemas"
}

Kotlin

plugins {
  id("androidx.room")
}

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

Veritabanı şemanız varyant, tür veya yapı türüne göre farklılık gösteriyorsa schemaDirectory() yapılandırmasını, her biri ilk bağımsız değişken olarak variantMatchName ile birden fazla kez kullanarak farklı konumlar belirtmeniz gerekir. Her yapılandırma, varyant adının basit bir karşılaştırmasına dayanarak bir veya daha fazla varyantı eşleştirebilir.

Bu açıklamaların kapsamlı olduğundan ve tüm varyantları kapsadığından emin olun. Diğer yapılandırmaların hiçbiriyle eşleşmeyen varyantları işlemek için variantMatchName içermeyen bir schemaDirectory() de ekleyebilirsiniz. Örneğin, iki derleme türü demo ve full ile iki derleme türü olan debug ve release içeren bir uygulamada aşağıdakiler geçerli yapılandırmalardır:

Modern

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")
}

Ek açıklama işlemci seçeneğini kullanarak şema konumunu ayarlayın

Room'un 2.5.2 veya daha eski bir sürümünü kullanıyorsanız ya da Room Gradle Eklentisi'ni kullanmıyorsanız room.schemaLocation ek açıklama işlemcisi seçeneğini kullanarak şema konumunu ayarlayın.

Bu dizindeki dosyalar bazı Gradle görevleri için giriş ve çıkış olarak kullanılır. Artımlı ve önbelleğe alınmış derlemelerin doğruluğu ve performansı için Gradle'a bu dizin hakkında bilgi vermek üzere Gradle'ın CommandLineArgumentProvider parametresini kullanmanız gerekir.

Öncelikle, aşağıda gösterilen RoomSchemaArgProvider sınıfını modülünüzün Gradle derleme dosyasına kopyalayın. Örnek sınıftaki asArguments() yöntemi, room.schemaLocation=${schemaDir.path} öğesini KSP öğesine geçirir. KAPT ve javac kullanıyorsanız bu değeri -Aroom.schemaLocation=${schemaDir.path} olarak değiştirin.

Modern

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}")
  }
}

Ardından, belirtilen şema diziniyle RoomSchemaArgProvider kullanmak için derleme seçeneklerini yapılandırın:

Modern

// 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"))
        )
      }
    }
  }
}

Tek bir taşıma işlemini test etme

Taşıma işlemlerinizi test etmeden önce Odadaki androidx.room:room-testing Maven yapısını test bağımlılıklarınıza ekleyin ve dışa aktarılan şemanın konumunu bir öğe klasörü olarak ekleyin:

build.gradle

Modern

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")
}

Test paketi, dışa aktarılan şema dosyalarını okuyabilen bir MigrationTestHelper sınıfı sağlar. Paket, oluşturulan veritabanlarını yönetebilmek için JUnit4 TestRule arayüzünü de uygular.

Aşağıdaki örnekte tek bir taşıma işlemine yönelik bir test gösterilmektedir:

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

Tüm taşıma işlemlerini test edin

Tek bir artımlı taşımayı test etmek mümkün olsa da, uygulamanızın veritabanı için tanımlanan tüm taşıma işlemlerini kapsayan bir test eklemenizi öneririz. Bu, yeni oluşturulan bir veritabanı örneği ile tanımlanan taşıma yollarını izleyen eski bir örnek arasında tutarsızlık olmamasını sağlamaya yardımcı olur.

Aşağıdaki örnekte, tanımlanan tüm taşıma işlemleri için bir test gösterilmektedir:

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

Eksik taşıma yollarını sorunsuz bir şekilde ele alın

Room, cihazdaki mevcut bir veritabanını geçerli sürüme yükseltmek için taşıma yolu bulamazsa IllegalStateException görüntülenir. Taşıma yolu eksik olduğunda mevcut verilerin kaybedilmesi kabul ediliyorsa veritabanını oluştururken fallbackToDestructiveMigration() oluşturucu yöntemini çağırın:

Kotlin

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

Java

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

Bu yöntem, kademeli bir taşıma gerçekleştirmesi gerektiğinde ve tanımlanmış bir taşıma yolu olmadığında Room'a uygulamanızın veritabanındaki tabloları yıkıcı bir şekilde yeniden oluşturmasını sağlar.

Room'un yalnızca belirli durumlarda yıkıcı eğlence içeriklerine geri dönmesini istiyorsanız fallbackToDestructiveMigration() için birkaç alternatif vardır:

  • Şema geçmişinizin belirli sürümleri, taşıma yollarıyla çözemediğiniz hatalara neden oluyorsa bunun yerine fallbackToDestructiveMigrationFrom() kullanın. Bu yöntem, Room'un yalnızca belirli sürümlerden taşıma gerçekleştirirken yıkıcı eğlence deneyimine geri dönmesini istediğinizi belirtir.
  • Room'un yalnızca daha yüksek bir veritabanı sürümünden daha eski bir veritabanına geçiş yaparken yıkıcı yeniden oluşturma işlemlerine geri dönmesini istiyorsanız bunun yerine fallbackToDestructiveMigrationOnDowngrade() aracını kullanın.

Oda 2.2.0 sürümüne geçerken sütunun varsayılan değerlerini işleyin

Oda 2.2.0 ve sonraki sürümlerde, @ColumnInfo(defaultValue = "...") ek açıklamasını kullanarak bir sütun için varsayılan değer tanımlayabilirsiniz. 2.2.0'dan düşük sürümlerde, bir sütun için varsayılan değeri tanımlamanın tek yolu, sütunu doğrudan yürütülen bir SQL ifadesinde tanımlamaktır. Bu, Room'un bilmediği bir varsayılan değer oluşturur. Bu, veritabanının ilk olarak Room'un 2.2.0'dan önceki bir sürümüyle oluşturulmuş olması durumunda, uygulamanızı Oda 2.2.0 kullanacak şekilde yükselttiğinizde, Room API'lerini kullanmadan tanımladığınız mevcut varsayılan değerler için özel bir taşıma yolu sağlamanız gerekebileceği anlamına gelir.

Örneğin, veritabanının 1. sürümünün bir Song varlığını tanımladığını varsayalım:

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

Aynı veritabanının sürüm 2'nin yeni bir NOT NULL sütunu eklediğini ve sürüm 1'den sürüm 2'ye bir taşıma yolu tanımladığını varsayalım:

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 ''");
    }
};

Bu durum, temel tabloda uygulamanın güncellemeleri ile yeni yüklemeleri arasında tutarsızlığa neden olur. tag sütununun varsayılan değeri yalnızca sürüm 1'den sürüm 2'ye kadar olan taşıma yolunda bildirildiğinden, uygulamayı sürüm 2'den itibaren yükleyen kullanıcıların şemalarında tag için varsayılan değer bulunmaz.

Room'un 2.2.0'dan düşük sürümlerinde, bu tutarsızlık herhangi bir sorun teşkil etmez. Ancak uygulama daha sonra Oda 2.2.0 veya sonraki bir sürümü kullanacak şekilde yükseltilirse ve Song varlık sınıfını @ColumnInfo ek açıklamasını kullanarak tag için varsayılan bir değer içerecek şekilde değiştirirse Room bu tutarsızlığı görebilir. Bu durum, şema doğrulamalarının başarısız olmasına yol açar.

Önceki taşıma yollarınızda sütun varsayılan değerleri belirtildiğinde veritabanı şemasının tüm kullanıcılar arasında tutarlı olmasını sağlamak için uygulamanızı Oda 2.2.0 veya sonraki bir sürümü kullanacak şekilde ilk kez yükseltirken aşağıdakileri yapın:

  1. @ColumnInfo ek açıklamasını kullanarak, sütun varsayılan değerlerini ilgili varlık sınıflarında bildirin.
  2. Veritabanı sürüm numarasını 1 artırın.
  3. Gerekli varsayılan değerleri mevcut sütunlara eklemek için bırak ve yeniden oluşturma stratejisini uygulayan yeni sürüme bir taşıma yolu tanımlayın.

Aşağıdaki örnekte bu süreç gösterilmektedir:

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