Добавьте столбец в существующую базу данных номеров, используя текущую функцию автоматической миграции, доступную в номере версии 2.4.0-alpha01

#android #sqlite #android-room

Вопрос:

У меня есть база данных комнат, содержащая данные, и теперь я хочу добавить в нее еще один столбец.

Версия комнаты 2.4.0-alpha01 и выше должна была упростить автоматическую миграцию, поэтому я использовал ее следующим образом:

 ...
version = 2,
autoMigrations = {
        @AutoMigration(from = 1, to = 2)
        }, 
exportSchema = true
 

Затем в моем классе модели я добавил новое имя столбца и сгенерировал его установщики и получатели, как и другие.

В документации помещения говорится, что если помещение не может выполнить миграцию из-за сложных изменений схемы, возникает ошибка времени компиляции. В моем случае, однако, я получаю ошибку во время выполнения о несоответствии между ожидаемой схемой и новой схемой (той, в которую я добавил столбец).

Ниже приведена ошибка:

 java.lang.RuntimeException: Exception while computing database live data.
        at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:92)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)
     Caused by: java.lang.IllegalStateException: Migration didn't properly handle: accounts(com.bisform.susu.models.Account).
     Expected:
    TableInfo{name='accounts', columns={accountSavings=Column{name='accountSavings', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountName=Column{name='accountName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountCreateTime=Column{name='accountCreateTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, numberOfDeposits=Column{name='numberOfDeposits', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, accountNumber=Column{name='accountNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, numberOfWithdrawals=Column{name='numberOfWithdrawals', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, accountPayoutDate=Column{name='accountPayoutDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, phoneOfNextOfKin=Column{name='phoneOfNextOfKin', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountImageURI=Column{name='accountImageURI', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountID=Column{name='accountID', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}, lastUpdatedDate=Column{name='lastUpdatedDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, phoneNumber=Column{name='phoneNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountPayoutTime=Column{name='accountPayoutTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, lastUpdatedTime=Column{name='lastUpdatedTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, nameOfNextOfKin=Column{name='nameOfNextOfKin', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountCreateDate=Column{name='accountCreateDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, isPaidOut=Column{name='isPaidOut', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
     Found:
E/AndroidRuntime: TableInfo{name='accounts', columns={accountSavings=Column{name='accountSavings', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountName=Column{name='accountName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountCreateTime=Column{name='accountCreateTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, numberOfDeposits=Column{name='numberOfDeposits', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, accountNumber=Column{name='accountNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, numberOfWithdrawals=Column{name='numberOfWithdrawals', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, accountPayoutDate=Column{name='accountPayoutDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountImageURI=Column{name='accountImageURI', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountID=Column{name='accountID', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}, lastUpdatedDate=Column{name='lastUpdatedDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, phoneNumber=Column{name='phoneNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountPayoutTime=Column{name='accountPayoutTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, lastUpdatedTime=Column{name='lastUpdatedTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountCreateDate=Column{name='accountCreateDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, isPaidOut=Column{name='isPaidOut', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
        at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:103)
...
 

Пожалуйста, что мне следует сделать, чтобы справиться с этой ошибкой? Как мне получить место для правильной миграции, используя существующую в настоящее время функцию автоматической миграции?

Ответ №1:

Пожалуйста, что мне следует сделать, чтобы справиться с этой ошибкой? Как мне получить место для правильной миграции, используя существующую в настоящее время функцию автоматической миграции?

Я считаю, что вы получите это только в том случае, если каким-то образом опустите Автоматическую миграцию. С помощью кода, который вы показали, я полагаю, что единственным способом было бы включить перенос вручную, поскольку это затем переопределит автоматическую миграцию.

Тестирование, как показано ниже, показывает это.

Тестирование

Это тестирование показывает эффект переопределения автоматической миграции с V1 на V2, а также то, как работает миграция, когда она не переопределена.

Тест основан на классе учетных записей, построенном в соответствии с вашими ожидаемыми (v2) и найденными (v1) сообщениями.

Таким образом, класс учетных записей является :-

 @Entity(tableName = "accounts")
class Accounts {

    //String phoneOfNextOfKin; //<<<<< For  V2 else commented out
    //String nameOfNextOfKin; //<<<<< for V2 else commented out

    String accountSavings;
    String accountName;
    String accountCreateTime;
    Integer numberOfDeposits;
    String accountNumber;
    int numberOfWithdrawals;
    String accountPayoutDate;
    String accountImageURI;
    @PrimaryKey
    Long accountId;
    String lastUpdatedDate;
    String phoneNumber;
    String accountPayoutTime;
    String lastUpdatedTime;
    String accountCreateDate;
    boolean isPaidOut;
}
 
  • Отмечая, что строки с комментарием //<<<<
  • т. е. 2 столбца phoneOfNextOfKin и nameOfNextOfKin добавлены для версии 2

Класс @Dao, который не меняется между версиями :-

 @Dao
abstract class AccountsDao {
    @Insert
    abstract long insert(Accounts accounts);
    @Query("SELECT * FROM accounts")
    abstract List<Accounts> getAllFromAccounts();
}
 

Класс @Database :-

 @Database(
        entities = {Accounts.class},
        version = TheDatabase.DBVERSION
        /* following line, if no schema saved, needs to be commented out */
        , autoMigrations = {@AutoMigration(from = 1, to = 2)} 
)
abstract class TheDatabase extends RoomDatabase {
    abstract AccountsDao getAccountsDao();

    public static final int DBVERSION = 1; //<<<<< change accordingly

    private static volatile TheDatabase instance = null;

    static TheDatabase getInstance(Context context) {
        if (instance == null) {
            instance = Room.databaseBuilder(
                    context,
                    TheDatabase.class,
                    "accounts.db"
            )
                    .allowMainThreadQueries() //run on main thread for brevity and convenience
                    .addMigrations(MIGRATION_1_2) //<<<<< if included then Migration didn't properly handle:
                    .build();
        }
        return instance;
    }

    /* Only needed for creating error shown in question */
    /* doesn't hurt if no addMigrations or when creating V1 */
    static final Migration MIGRATION_1_2 = new Migration(1,2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {

        }
    };
}
 

Наконец, следующее мероприятие :-

 public class MainActivity extends AppCompatActivity {

    TheDatabase db;
    AccountsDao dao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        db = TheDatabase.getInstance(this);
        dao = db.getAccountsDao();

        Accounts a = new Accounts();
        a.accountCreateDate = "2021-01-01";
        a.accountCreateTime = "08:00:00";
        a.accountImageURI = "imageURI";
        a.accountName = "MyAccount";
        a.accountNumber = "0000000000";
        a.accountPayoutDate = "2021-05-05";
        a.accountPayoutTime = "10:00:00";
        a.accountSavings = "savings";
        a.lastUpdatedDate = "2021-05-03";
        a.lastUpdatedTime = "09:15:00";
        a.numberOfDeposits = 10;
        a.numberOfWithdrawals = 5;
        a.isPaidOut = false;

        //a.nameOfNextOfKin = "Mr Next of Kin"; //<<<<< For V2
        //a.phoneOfNextOfKin = "1111111111"; //<<<<< For V2

        dao.insert(a);
        for(Accounts account: dao.getAllFromAccounts()) {
            String msg = "Account is "   account.accountName   " ID is "   account.accountId;
            if (TheDatabase.DBVERSION > 1) {
                //msg = msg   " next of kin is "   account.nameOfNextOfKin; //<<<<< For V2
            }
            Log.d("ACCOUNTINFO", msg);
        }
    }
}
 

Пробеги и результаты:-

  1. С кодом (код V2 не включен), как указано выше, и с не установленным приложением. т. е. Создайте базу данных V1:-
  • Приложение успешно запускается, и инспектор базы данных показывает :-
  • введите описание изображения здесь
  • Схема соответствует ожиданиям, как и одна строка.
  1. Запустите снова без каких-либо изменений
  • Приложение успешно запускается, и инспектор базы данных показывает :-

введите описание изображения здесь

3. Изменения, внесенные для перехода на версию 2, в частности:-

  • в аккаунтах указаны имя и телефон ближайших родственников.
  • в базе данных DBVERSION изменено с 1 на 2 и .Добавленные изменения остались включенными
  • в вызывающем действии значения имени и ближайших родственников присваиваются значениям, а не закомментируются. переменная msg объединяется для включения ближайших родственников.
  • Приложение вылетает с

:-

 Caused by: java.lang.IllegalStateException: Migration didn't properly handle: accounts(a.a.so69442030javaroomautomigrationsaddcolumns.Accounts).
     Expected:
2021-10-05 14:31:50.669 30268-30268/a.a.so69442030javaroomautomigrationsaddcolumns E/AndroidRuntime: TableInfo{name='accounts', columns={accountSavings=Column{name='accountSavings', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountName=Column{name='accountName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountCreateTime=Column{name='accountCreateTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, numberOfDeposits=Column{name='numberOfDeposits', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountNumber=Column{name='accountNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, numberOfWithdrawals=Column{name='numberOfWithdrawals', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, phoneOfNextOfKin=Column{name='phoneOfNextOfKin', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountPayoutDate=Column{name='accountPayoutDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountImageURI=Column{name='accountImageURI', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountId=Column{name='accountId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}, lastUpdatedDate=Column{name='lastUpdatedDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, phoneNumber=Column{name='phoneNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountPayoutTime=Column{name='accountPayoutTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, nameOfNextOfKin=Column{name='nameOfNextOfKin', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, lastUpdatedTime=Column{name='lastUpdatedTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountCreateDate=Column{name='accountCreateDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, isPaidOut=Column{name='isPaidOut', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
     Found:
2021-10-05 14:31:50.670 30268-30268/a.a.so69442030javaroomautomigrationsaddcolumns E/AndroidRuntime: TableInfo{name='accounts', columns={accountSavings=Column{name='accountSavings', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountName=Column{name='accountName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountCreateTime=Column{name='accountCreateTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, numberOfDeposits=Column{name='numberOfDeposits', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountNumber=Column{name='accountNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, numberOfWithdrawals=Column{name='numberOfWithdrawals', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, accountPayoutDate=Column{name='accountPayoutDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountImageURI=Column{name='accountImageURI', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountId=Column{name='accountId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}, lastUpdatedDate=Column{name='lastUpdatedDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, phoneNumber=Column{name='phoneNumber', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountPayoutTime=Column{name='accountPayoutTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, lastUpdatedTime=Column{name='lastUpdatedTime', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, accountCreateDate=Column{name='accountCreateDate', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, isPaidOut=Column{name='isPaidOut', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
        at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:103)
 
  • т. е. повторение возникшего сбоя
  1. Шаги 1-2 были повторены после удаления приложения и использования кода в соответствии с версией V1 (для демонстрации всего процесса с нуля)(повторный запуск с комментариями .addMigrations или удалением устранит проблему)
  2. Изменения в соответствии с шагом 3 были применены, НО ДОПОЛНИТЕЛЬНО строка .addMigrations в сборке базы данных была закомментирована.
  • Приложение не выходит из строя. Инспектор базы данных показывает измененную схему и соответствующие данные во вновь вставленной строке (остальные 2 имеют значения null для двух добавленных столбцов). :-

введите описание изображения здесь

Комментарии:

1. большое спасибо. Но разве удаление приложения и его переустановка не означает, что база данных полностью уничтожена и создана заново? следовательно, вы теряете все существующие данные? И если это так, создаются совершенно новая база данных и схема, следовательно, вы не получите ожидаемую схему и исключение «схема найдена»?

2. @ZiyaadShiraz Да, это правильно. Однако я не говорил об удалении в качестве исправления. Я удалил, чтобы продемонстрировать полный цикл (создайте v1, добавьте еще несколько данных в V1, выполните автоматическую миграцию (без переноса предполагаемой проблемы ) в V2). Я мог бы просто перейти на V2 после сбоя, удалив миграцию

Ответ №2:

Все, что потребовалось для устранения вышеуказанной проблемы, — это очистить проект и перестроить его. Я предполагаю, что старая схема каким-то образом все еще была в памяти, отсюда сбои.