Путаница в требовании перехода по умолчанию для обновления после Room 2.1.0 - PullRequest
2 голосов
/ 11 января 2020

В комнате 2.1.0 обычно используется следующий код

Версия 2

@Entity(tableName = "password")
public class Password {
    @ColumnInfo(name = "dummy0")
    @NonNull
    public String dummy0;

}

public class Migration_1_2 extends Migration {
    public Migration_1_2() {
        super(1, 2);
    }

    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE password ADD COLUMN dummy0 TEXT NOT NULL DEFAULT ''");
    }
}

Руководство по миграции с

весьма запутаны.

Примечание. Если ваша схема базы данных уже имеет значения по умолчанию, например, добавленные через ALTER TABLE x ADD COLUMN y INTEGER NOTNULL DEFAULT z, и вы решили определить значения по умолчанию через @ColumnInfo для тех же столбцов, то вам может потребоваться указать миграция для проверки неучтенных значений по умолчанию. Для получения дополнительной информации см. Миграции помещений.


Перед обновлением до 2.2.3 существует 2 варианта

  1. У нас есть столбец dummy0 со значением по умолчанию, если выполняется миграция.
  2. Или у нас есть столбец dummy0 без значения по умолчанию, если это fre sh DB.

При обновлении до Room 2.1.0 до Room 2.2 .3, все по-прежнему работает нормально для обоих двух случаев, без добавления дополнительного кода миграции, для таблицы удаления и повторного создания.

Мы проводим дальнейшее тестирование.

Версия 3

@Entity(tableName = "password")
public class Password {
    @ColumnInfo(name = "dummy0")
    @NonNull
    public String dummy0;

    @ColumnInfo(name = "dummy1")
    @NonNull
    public String dummy1;
}

public class Migration_2_3 extends Migration {
    public Migration_2_3() {
        super(2, 3);
    }

    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE password ADD COLUMN dummy1 TEXT NOT NULL DEFAULT ''");
    }
}

Все еще работает нормально.

Версия 4

@Entity(tableName = "password")
public class Password {
    @ColumnInfo(name = "dummy0")
    @NonNull
    public String dummy0;

    @ColumnInfo(name = "dummy1")
    @NonNull
    public String dummy1;

    @ColumnInfo(name = "dummy2", defaultValue = "")
    @NonNull
    public String dummy2;
}

public class Migration_3_4 extends Migration {
    public Migration_3_4() {
        super(3, 4);
    }

    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE password ADD COLUMN dummy2 TEXT NOT NULL DEFAULT ''");
    }
}

Все еще работает нормально.


Итак, я путаюсь? При каком сценарии использования нам нужно фактически удалить и воссоздать таблицу?

1 Ответ

1 голос
/ 11 января 2020

Я считаю, что проблема не в добавлении нового столбца, а в том, что значение по умолчанию применено / изменено / удалено к существующему столбцу. Тогда вам, возможно, придется воссоздать соответствующие таблицы.

, например, если вы изменили: -

@ColumnInfo(name = "dummy0")
@NonNull
public String dummy0;

, чтобы добавить значение по умолчанию

@ColumnInfo(name = "dummy0", defaultValue = "")
@NonNull
public String dummy0;

Тогда будет несоответствие схемы, потому что ожидаемая схема будет иметь DEFAULT '', в то время как найденная схема (исходная база данных) не имеет кодировки по умолчанию.

  • Это потребует, чтобы таблица была удалена и воссоздана как Вы не можете изменить параметры атрибута столбца ALTER.

Если до 2.2.0 у вас была предыдущая не сгенерированная некоммерческая схема, которая включала значения по умолчанию и объекты не были изменены соответствующим образом, то вы бы сделали это. получите cla sh, потому что ожидаемая схема не имеет значения по умолчанию, в то время как найденная схема содержит DEFAULT = ''.

  • Это потребует соответствующего изменения сущностей.

Пример

Предполагается, что текущий объект: -

@Entity(tableName = "password")
public class Password {
    @PrimaryKey
    public Long id;
    @ColumnInfo(name = "dummy0")
    @NonNull
    public String dummy0;

    @ColumnInfo(name = "dummy1")
    @NonNull
    public String dummy1;
}

Тогда сгенерированный код для создания таблицы: -

_db.execSQL("CREATE TABLE IF NOT EXISTS `password` (`id` INTEGER, `dummy0` TEXT NOT NULL, `dummy1` TEXT NOT NULL, PRIMARY KEY(`id`))");
  • Приложение было запустить с вышеупомянутым, чтобы создать e база данных.

Если теперь для Версии 2 она изменена на: -

@Entity(tableName = "password")
public class Password {
    @PrimaryKey
    public Long id;
    @ColumnInfo(name = "dummy0", defaultValue = "" /*<<<<<<<<<< ADDED */)
    @NonNull
    public String dummy0;

    @ColumnInfo(name = "dummy1")
    @NonNull
    public String dummy1;
}

Тогда сгенерированный код: -

_db.execSQL("CREATE TABLE IF NOT EXISTS `password` (`id` INTEGER, `dummy0` TEXT NOT NULL DEFAULT '', `dummy1` TEXT NOT NULL, PRIMARY KEY(`id`))");

Выполняется с немой / пустой миграцией (1-2), затем: -

  • Найденная схема (исходная БД) имеет: - defaultValue='null'
  • Но ожидаемая схема имеет: - defaultValue=''''

Согласно: -

2020-01-11 19:11:15.300 12539-12539/a.so59691979 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: a.so59691979, PID: 12539
    java.lang.RuntimeException: Unable to start activity ComponentInfo{a.so59691979/a.so59691979.MainActivity}: java.lang.IllegalStateException: Migration didn't properly handle: password(a.so59691979.Password).
     Expected:
    TableInfo{name='password', columns={dummy0=Column{name='dummy0', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue=''''}, dummy1=Column{name='dummy1', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
     Found:
    TableInfo{name='password', columns={dummy0=Column{name='dummy0', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, dummy1=Column{name='dummy1', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3270)

Пример исправления

Использование миграции: -

Migration M1_2 = new Migration(1,2) {
    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {

        // CREATE SQL Copied from the generated Java PasswordDatabase_Impl (name changed)
        final String SQL_CREATE_NEW_PASSWORDTABLE =
                "CREATE TABLE IF NOT EXISTS `password_new` (`id` INTEGER, `dummy0` TEXT NOT NULL DEFAULT '', `dummy1` TEXT NOT NULL, PRIMARY KEY(`id`))";
        database.execSQL(SQL_CREATE_NEW_PASSWORDTABLE);
        database.execSQL("INSERT INTO `password_new` SELECT * FROM `password`");
        database.execSQL("ALTER TABLE `password` RENAME TO `password_old`");
        database.execSQL("ALTER TABLE `password_new` RENAME TO `password`");
        database.execSQL("DROP TABLE IF EXISTS `password_old`");
    }
}

Устраняет проблему .

Код

Для создания вышеуказанного кода использовался следующий код: -

Пароль. java

/*
//Original
@Entity(tableName = "password")
public class Password {
    @PrimaryKey
    public Long id;
    @ColumnInfo(name = "dummy0")
    @NonNull
    public String dummy0;

    @ColumnInfo(name = "dummy1")
    @NonNull
    public String dummy1;
}
*/

// New
@Entity(tableName = "password")
public class Password {
    @PrimaryKey
    public Long id;
    @ColumnInfo(name = "dummy0", defaultValue = "" /*<<<<<<<<<< ADDED */)
    @NonNull
    public String dummy0;

    @ColumnInfo(name = "dummy1")
    @NonNull
    public String dummy1;
}
  • Первоначально использовался оригинал

PasswordDatabase. java

@Database(version = 2, entities = {Password.class})
public abstract class PasswordDatabase extends RoomDatabase {
}
  • Исходная версия была 1

MainActivity. java

publi c Класс MainActivity расширяет AppCompatActivity {

PasswordDatabase passwordDatabase;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    passwordDatabase = Room.databaseBuilder(
            this,
            PasswordDatabase.class,
            "passworddb"
    )
            .allowMainThreadQueries()
            .addMigrations(M1_2)
            .build();
    passwordDatabase.getOpenHelper().getWritableDatabase();
}

Migration M1_2 = new Migration(1,2) {
    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {

        // CREATE SQL Copied from the generated Java PasswordDatabase_Impl (name changed)
        final String SQL_CREATE_NEW_PASSWORDTABLE =
                "CREATE TABLE IF NOT EXISTS `password_new` (`id` INTEGER, `dummy0` TEXT NOT NULL DEFAULT '', `dummy1` TEXT NOT NULL, PRIMARY KEY(`id`))";
        database.execSQL(SQL_CREATE_NEW_PASSWORDTABLE);
        database.execSQL("INSERT INTO `password_new` SELECT * FROM `password`");
        database.execSQL("ALTER TABLE `password` RENAME TO `password_old`");
        database.execSQL("ALTER TABLE `password_new` RENAME TO `password`");
        database.execSQL("DROP TABLE IF EXISTS `password_old`");
    }
};
  • Изначально тело M1_2 было пустым (поэтому как заставить ошибку)
...