Migración de la base de datos de la sala si solo se agrega una nueva tabla

99

Supongamos que tengo una base de datos de habitaciones simple:

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

Ahora, estoy agregando una nueva entidad: Pety mejorando la versión a 2:

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

Por supuesto, Room lanza una excepción: java.lang.IllegalStateException: A migration from 1 to 2 is necessary.

Suponiendo que no he cambiado de Userclase (por lo que todos los datos están seguros), tengo que proporcionar una migración que solo crea una nueva tabla. Entonces, estoy buscando clases generadas por Room, buscando la consulta generada para crear mi nueva tabla, copiándola y pegando en la migración:

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

Sin embargo, me resulta inconveniente hacerlo manualmente. ¿Hay alguna manera de decirle a Room? No estoy tocando ninguna de las tablas existentes, por lo que los datos están seguros. ¿Crear una migración para mí?

Piotr Aleksander Chmielowski
fuente
¿Ha encontrado una solución para esto?
Mikkel Larsen
3
Tuve el mismo problema y lo solucioné de la misma manera que tú, y tampoco he encontrado una solución. Me alegro de no estar solo entonces. :)
Mikkel Larsen
3
Igual que aquí. Me parece muy inconveniente que room pueda generar la consulta de creación dentro de database_impl, pero no puede simplemente crear la tabla, una vez que comienza la migración ...
JacksOnF1re
1
Daría mucho por tal característica ... También sería bueno mezclar migraciones y el mecanismo de
respaldo
3
No estoy seguro de si esto sería útil, pero Room tiene la opción de exportar el esquema de la base de datos a un archivo JSON. developer.android.com/training/data-storage/room/… Obviamente, esto aún significaría agregar manualmente el script de migración, pero no necesitaría enrutar las clases generadas automáticamente para obtener su declaración SQL.
James Lendrem

Respuestas:

76

Habitación no no tiene un buen sistema de migración, al menos no hasta 2.1.0-alpha03.

Entonces, hasta que tengamos un mejor sistema de migración, hay algunas soluciones para tener migraciones fáciles en la sala.

Como no existe un método como @Database(createNewTables = true) o MigrationSystem.createTable(User::class), que debería haber uno u otro, la única forma posible es ejecutar

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

dentro de tu migratemétodo.

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

Para obtener el script SQL anterior , tiene 4 formas

1. Escribe tú mismo

Básicamente, debe escribir el script anterior que coincidirá con el script que genera Room. De esta manera es posible, no factible. (Considere que tiene 50 campos)

2. Exportar esquema

Si incluye exportSchema = truedentro de su @Databaseanotación, Room generará un esquema de base de datos dentro de / esquemas de la carpeta de su proyecto. El uso es

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

Asegúrese de haber incluido las siguientes líneas en build.gradeel módulo de su aplicación

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

Cuando ejecute o compile el proyecto, obtendrá un archivo JSON 2.json, que tiene todas las consultas dentro de su base de datos de 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
          },

Por lo tanto, puede incluir lo anterior createSqldentro de su migratemétodo.

3. Obtener consulta de AppDatabase_Impl

Si no desea exportar el esquema, aún puede obtener la consulta ejecutando o construyendo el proyecto que generará el AppDatabase_Impl.javaarchivo. y dentro del archivo especificado puede tener.

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

Dentro del createAllTablesmétodo, habrá scripts de creación de todas las entidades. Puede obtenerlo e incluirlo dentro de su migratemétodo.

4. Procesamiento de anotaciones.

Como puede adivinar, Room genera todo lo mencionado anteriormente schemay AppDatabase_Implarchivos dentro del tiempo de compilación y con el procesamiento de anotaciones que agrega con

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

Eso significa que también puede hacer lo mismo y crear su propia biblioteca de procesamiento de anotaciones que genera todas las consultas de creación necesarias para usted.

La idea es crear una biblioteca de procesamiento de anotaciones para las anotaciones de habitaciones de @Entityy @Database. Tome una clase con anotaciones, @Entitypor ejemplo. Estos son los pasos que tendrás que seguir

  1. Cree una nueva StringBuildery agregue "CREAR TABLA SI NO EXISTE"
  2. Obtenga el nombre de la tabla desde class.simplenameo por tableNamecampo de @Entity. Agrégalo a tuStringBuilder
  3. Luego, para cada campo de su clase, cree columnas de SQL. Tome el nombre, el tipo, la posibilidad de nulidad del campo, ya sea por el campo en sí o por @ColumnInfoanotación. Para cada campo, debe agregar el id INTEGER NOT NULLestilo de una columna a su StringBuilder.
  4. Agregar claves primarias por @PrimaryKey
  5. Agregue ForeignKeyy Indicessi existe.
  6. Después de terminar, conviértalo en cadena y guárdelo en alguna clase nueva que desee usar. Por ejemplo, guárdelo como se muestra a continuación
public final class UserSqlUtils {
  public String createTable = "CREATE TABLE IF NOT EXISTS User (id INTEGER, PRIMARY KEY(id))";
}

Entonces, puedes usarlo como

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

Hice una biblioteca de este tipo para mí mismo que puede consultar e incluso usarla en su proyecto. Tenga en cuenta que la biblioteca que hice no está llena y solo cumple con mis requisitos para la creación de tablas.

RoomExtension para una mejor migración

Aplicación que usa RoomExtension

Espero que haya sido útil.

ACTUALIZAR

En el momento de escribir esta respuesta, la versión de la sala era 2.1.0-alpha03y cuando envié un correo electrónico a los desarrolladores recibí una respuesta de

Se espera tener un mejor sistema de migración en 2.2.0

Desafortunadamente, todavía nos falta un mejor sistema de migración.

musooff
fuente
3
¿Puede señalar dónde leyó que Room 2.2.x tendrá una mejor migración? No puedo encontrar nada que haga esa afirmación, y dado que actualmente estamos trabajando en 2.1.0 beta, lo que está en 2.2.0 parece ser desconocido en este momento.
jkane001
1
@ jkane001 Le envié un correo electrónico a uno de los desarrolladores de la sala y obtuve una respuesta. No hay tal aviso público con respecto a 2.2.x (¿todavía?)
musooff
4
Actualmente en la versión 2.2.2 y aún no hay una mejor migración :( Sin embargo, esta es una respuesta excelente y me ahorró un montón de trabajo, así que +1 para eso.
smitty1
@androiddeveloper Todos excepto el # 4, que es el procesamiento de anotaciones
musooff
1
@musooff Creo que está bien agregar la creación de la tabla. Es la forma más segura de copiar el código de la función "createAllTables".
desarrollador de Android
3

Lo sentimos, Room no admite la creación automática de tablas sin pérdida de datos.

Es obligatorio escribir la migración. De lo contrario, borrará todos los datos y creará la nueva estructura de tabla.

Viswanath Kumar Sandu
fuente
0

Puedes hacerlo de esta manera

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

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

El resto será el mismo que ha mencionado anteriormente.

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

Referencia - Para más

Sujeet Kumar
fuente
0

Puede agregar el siguiente comando gradle a su defaultConfig en su app.gradle:

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

Cuando ejecute esto, compilará una lista de nombres de tablas con sus declaraciones CREATE TABLE relevantes desde las cuales puede copiar y pegar en sus objetos de migración. Puede que tenga que cambiar los nombres de las tablas.

Por ejemplo, esto es de mi esquema generado:

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

Y entonces copio y pego la declaración createSql y cambio '$ {TABLE_NAME}' a 'assets' el nombre de la tabla, y listo, las declaraciones de creación de salas generadas automáticamente.

Larry Stent
fuente
-1

En este caso, no necesita realizar una migración, puede llamar a .fallbackToDestructiveMigration () cuando esté creando una instancia de base de datos.

Ejemplo:

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

Y no olvide cambiar la versión de la base de datos.

rudicjovan
fuente
Esta solución eliminará todos mis datos de las tablas existentes.
Piotr Aleksander Chmielowski
Por desgracia sí. "Puede llamar a este método para cambiar este comportamiento y volver a crear la base de datos en lugar de fallar. Tenga en cuenta que esto eliminará todos los datos en las tablas de la base de datos administradas por Room".
rudicjovan
-2

¿Quizás en este caso (si solo ha creado una nueva tabla sin cambiar otras) pueda hacer esto sin crear ninguna migración?

usuario1730694
fuente
1
No, en este caso, la sala arroja registros: java.lang.IllegalStateException: es necesaria una migración de {old_version} a {new_version}
Max Makeichik