#android #android-sqlite #android-room
Вопрос:
Я повсюду искал причину этой ошибки:
TableInfo{name='recipes', columns={cook_time=Column{name='cook_time', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, tier_rating=Column{name='tier_rating', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, notes=Column{name='notes', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, tom_rating=Column{name='tom_rating', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, prep_time=Column{name='prep_time', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, serves=Column{name='serves', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='1'}, name=Column{name='name', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}, url=Column{name='url', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[Index{name='index_recipes_name', unique=true, columns=[name]}]} Found: TableInfo{name='recipes', columns={}, foreignKeys=[], indices=[]} at androidx.room.RoomOpenHelper.checkIdentity(RoomOpenHelper.java:163) at androidx.room.RoomOpenHelper.onOpen(RoomOpenHelper.java:135) at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onOpen(FrameworkSQLiteOpenHelper.java:195) at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:428) at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:317) at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:145) at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:106) at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:622) at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:399) at androidx.room.RoomDatabase.query(RoomDatabase.java:442) at androidx.room.util.DBUtil.query(DBUtil.java:83) at com.example.shoppinglistapp2.db.dao.IngListItemDao_Impl$10.call(IngListItemDao_Impl.java:1036) at com.example.shoppinglistapp2.db.dao.IngListItemDao_Impl$10.call(IngListItemDao_Impl.java:1033) at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:90)
Каждая другая аналогичная проблема, по-видимому, имеет по крайней мере НЕКОТОРЫЕ элементы таблицы или использует функцию databaseBuilder.createFromAsset. Я, однако, нет, мой код базы данных вставлен ниже. Я попытался удалить все свои миграции и просто вернуться к разрушительной миграции. Я просмотрел экспортированные файлы схем, которые четко отображают все свойства правильно. В моем приложении все работало нормально, пока я не попытался установить его на новое устройство, на котором не было существующего экземпляра базы данных. Я могу нормально запустить его на устройстве с существующими данными базы данных из предыдущих сборок/использования приложения, но новые установки выходят из строя сразу при запуске, и я получаю вышеуказанную ошибку. Я не пробовал этого раньше, поэтому не знаю, в какой момент могла возникнуть эта проблема.
Я использовал версию комнаты «2.4.0-alpha01», но я также попытался вернуться к версии 2.3.0 без каких-либо изменений. Может ли быть какая-то внешняя библиотека, которая пытается предварительно заполнить, о которой я не знаю? Я не уверен, где он даже находит схему с пустыми таблицами, отображаемыми в разделе «найдено» — у меня даже нет папки /assets, из которой он мог бы пытаться получить createFromAsset
ресурс, даже если бы я это называл.
SlaDatabase.java
@Database(entities = {Recipe.class, IngList.class, IngListItem.class, Tag.class, MealPlan.class, Meal.class}, version = 17, exportSchema = true // autoMigrations = { // @AutoMigration(from = 15,to = 16), // @AutoMigration(from = 16, to = 17) // } ) public abstract class SlaDatabase extends RoomDatabase { public abstract RecipeDao recipeDao(); public abstract IngListItemDao ingListItemDao(); public abstract MealDao mealDao(); public abstract TagDao tagDao(); public abstract MealPlanDao mealPlanDao(); public abstract IngListDao ingListDao(); private static volatile SlaDatabase INSTANCE; private static final int NUMBER_OF_THREADS = 4; static final ListeningExecutorService databaseWriteExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(NUMBER_OF_THREADS)); static SlaDatabase getDatabase(final Context context) { if (INSTANCE == null) { synchronized (SlaDatabase.class) { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder(context.getApplicationContext(), SlaDatabase.class, "sla_database") .fallbackToDestructiveMigration() .addCallback(sRoomDatabaseCallback) .build(); } } } return INSTANCE; } //ensure the shopping list IngList (id = 0) exists private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback(){ @Override public void onCreate(@NonNull SupportSQLiteDatabase db) { super.onCreate(db); db.beginTransaction(); try { db.execSQL("INSERT OR IGNORE INTO ing_lists DEFAULT VALUES"); } catch(Exception e){ Log.d("TOM_TEST", e.toString());; } finally { db.endTransaction(); } } };
Recipe.java
@Entity( tableName = "recipes", indices = {@Index(value = {"name"}, unique = true)}//the recipe name must be unique ) public class Recipe { @PrimaryKey(autoGenerate = true) private int id; @NonNull private String name; @ColumnInfo(name="prep_time") private int prepTime; @ColumnInfo(name = "cook_time") private int cookTime; @ColumnInfo(defaultValue = "1") private int serves = 1; private String url; private String notes; private int tom_rating; private int tier_rating; //getters and setters...
Exported schema for version 17 of SlaDatabase
{ "formatVersion": 1, "database": { "version": 17, "identityHash": "fe979e62da4f94c5bb89144e955361e0", "entities": [ { "tableName": "recipes", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `prep_time` INTEGER NOT NULL, `cook_time` INTEGER NOT NULL, `serves` INTEGER NOT NULL DEFAULT 1, `url` TEXT, `notes` TEXT, `tom_rating` INTEGER NOT NULL, `tier_rating` INTEGER NOT NULL)", "fields": [ { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "name", "columnName": "name", "affinity": "TEXT", "notNull": true }, { "fieldPath": "prepTime", "columnName": "prep_time", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "cookTime", "columnName": "cook_time", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "serves", "columnName": "serves", "affinity": "INTEGER", "notNull": true, "defaultValue": "1" }, { "fieldPath": "url", "columnName": "url", "affinity": "TEXT", "notNull": false }, { "fieldPath": "notes", "columnName": "notes", "affinity": "TEXT", "notNull": false }, { "fieldPath": "tom_rating", "columnName": "tom_rating", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "tier_rating", "columnName": "tier_rating", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "columnNames": [ "id" ], "autoGenerate": true }, "indices": [ { "name": "index_recipes_name", "unique": true, "columnNames": [ "name" ], "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_recipes_name` ON `${TABLE_NAME}` (`name`)" } ], "foreignKeys": [] }, { "tableName": "ing_lists", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` INTEGER, `meal_plan_id` INTEGER, FOREIGN KEY(`recipe_id`) REFERENCES `recipes`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`meal_plan_id`) REFERENCES `meal_plans`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "recipeId", "columnName": "recipe_id", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "mealPlanId", "columnName": "meal_plan_id", "affinity": "INTEGER", "notNull": false } ], "primaryKey": { "columnNames": [ "id" ], "autoGenerate": true }, "indices": [ { "name": "index_ing_lists_recipe_id", "unique": false, "columnNames": [ "recipe_id" ], "createSql": "CREATE INDEX IF NOT EXISTS `index_ing_lists_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)" }, { "name": "index_ing_lists_meal_plan_id", "unique": false, "columnNames": [ "meal_plan_id" ], "createSql": "CREATE INDEX IF NOT EXISTS `index_ing_lists_meal_plan_id` ON `${TABLE_NAME}` (`meal_plan_id`)" } ], "foreignKeys": [ { "table": "recipes", "onDelete": "CASCADE", "onUpdate": "NO ACTION", "columns": [ "recipe_id" ], "referencedColumns": [ "id" ] }, { "table": "meal_plans", "onDelete": "CASCADE", "onUpdate": "NO ACTION", "columns": [ "meal_plan_id" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "ing_list_items", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `list_id` INTEGER NOT NULL, `volume_unit` TEXT, `volume_qty` REAL NOT NULL, `mass_unit` TEXT, `mass_qty` REAL NOT NULL, `whole_item_qty` REAL NOT NULL, `other_unit` TEXT, `other_qty` REAL NOT NULL, `checked` INTEGER NOT NULL DEFAULT 0, `list_order` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`list_id`) REFERENCES `ing_lists`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "name", "columnName": "name", "affinity": "TEXT", "notNull": false }, { "fieldPath": "listId", "columnName": "list_id", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "volumeUnit", "columnName": "volume_unit", "affinity": "TEXT", "notNull": false }, { "fieldPath": "volumeQty", "columnName": "volume_qty", "affinity": "REAL", "notNull": true }, { "fieldPath": "massUnit", "columnName": "mass_unit", "affinity": "TEXT", "notNull": false }, { "fieldPath": "massQty", "columnName": "mass_qty", "affinity": "REAL", "notNull": true }, { "fieldPath": "wholeItemQty", "columnName": "whole_item_qty", "affinity": "REAL", "notNull": true }, { "fieldPath": "otherUnit", "columnName": "other_unit", "affinity": "TEXT", "notNull": false }, { "fieldPath": "otherQty", "columnName": "other_qty", "affinity": "REAL", "notNull": true }, { "fieldPath": "checked", "columnName": "checked", "affinity": "INTEGER", "notNull": true, "defaultValue": "0" }, { "fieldPath": "listOrder", "columnName": "list_order", "affinity": "INTEGER", "notNull": true, "defaultValue": "0" } ], "primaryKey": { "columnNames": [ "id" ], "autoGenerate": true }, "indices": [ { "name": "index_ing_list_items_name", "unique": false, "columnNames": [ "name" ], "createSql": "CREATE INDEX IF NOT EXISTS `index_ing_list_items_name` ON `${TABLE_NAME}` (`name`)" }, { "name": "index_ing_list_items_list_id", "unique": false, "columnNames": [ "list_id" ], "createSql": "CREATE INDEX IF NOT EXISTS `index_ing_list_items_list_id` ON `${TABLE_NAME}` (`list_id`)" } ], "foreignKeys": [ { "table": "ing_lists", "onDelete": "CASCADE", "onUpdate": "NO ACTION", "columns": [ "list_id" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "tags", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `recipe_id` INTEGER NOT NULL, FOREIGN KEY(`recipe_id`) REFERENCES `recipes`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "name", "columnName": "name", "affinity": "TEXT", "notNull": true }, { "fieldPath": "recipeId", "columnName": "recipe_id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "columnNames": [ "id" ], "autoGenerate": true }, "indices": [ { "name": "index_tags_recipe_id", "unique": false, "columnNames": [ "recipe_id" ], "createSql": "CREATE INDEX IF NOT EXISTS `index_tags_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)" } ], "foreignKeys": [ { "table": "recipes", "onDelete": "CASCADE", "onUpdate": "NO ACTION", "columns": [ "recipe_id" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "meal_plans", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT)", "fields": [ { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "name", "columnName": "name", "affinity": "TEXT", "notNull": false } ], "primaryKey": { "columnNames": [ "id" ], "autoGenerate": true }, "indices": [], "foreignKeys": [] }, { "tableName": "meals", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `plan_id` INTEGER NOT NULL, `day_id` INTEGER NOT NULL, `day_title` TEXT, `recipe_id` INTEGER, `notes` TEXT, FOREIGN KEY(`recipe_id`) REFERENCES `recipes`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`plan_id`) REFERENCES `meal_plans`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "planId", "columnName": "plan_id", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "dayId", "columnName": "day_id", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "dayTitle", "columnName": "day_title", "affinity": "TEXT", "notNull": false }, { "fieldPath": "recipeId", "columnName": "recipe_id", "affinity": "INTEGER", "notNull": false }, { "fieldPath": "notes", "columnName": "notes", "affinity": "TEXT", "notNull": false } ], "primaryKey": { "columnNames": [ "id" ], "autoGenerate": true }, "indices": [ { "name": "index_meals_plan_id", "unique": false, "columnNames": [ "plan_id" ], "createSql": "CREATE INDEX IF NOT EXISTS `index_meals_plan_id` ON `${TABLE_NAME}` (`plan_id`)" }, { "name": "index_meals_recipe_id", "unique": false, "columnNames": [ "recipe_id" ], "createSql": "CREATE INDEX IF NOT EXISTS `index_meals_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)" } ], "foreignKeys": [ { "table": "recipes", "onDelete": "SET NULL", "onUpdate": "NO ACTION", "columns": [ "recipe_id" ], "referencedColumns": [ "id" ] }, { "table": "meal_plans", "onDelete": "CASCADE", "onUpdate": "NO ACTION", "columns": [ "plan_id" ], "referencedColumns": [ "id" ] } ] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fe979e62da4f94c5bb89144e955361e0')" ] } }
Ответ №1:
Вы завершаете транзакцию без использования setTransactionSuccessful
. Без endTransaction
этого будет откат изменений, эффективно отменяя изменения, включая создание таблиц, созданных Комнатой.
Таким образом, из-за отката первая проверка таблицы не находит таблицы (Рецепта), которая соответствует ожидаемой таблице (Рецепта), следовательно, найденная таблица не содержит столбцов и т. Д.
- В нем говорится о предварительно упакованной базе данных, потому что Room, по-видимому, предполагает, что эта несоответствующая база данных должна была быть откуда-то взята (это не то, что создаст Room); как таковая, она немного вводит в заблуждение (но что она должна сказать? (риторический)).
Вам необходимо использовать/применить setTransactionSuccessful()
метод, например
private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback() { @Override public void onCreate(@NonNull SupportSQLiteDatabase db) { super.onCreate(db); db.beginTransaction(); try { db.execSQL("INSERT OR IGNORE INTO ing_lists DEFAULT VALUES"); } catch (Exception e) { Log.d("TOM_TEST", e.toString()); } finally { db.setTransactionSuccessful(); //lt;lt;lt;lt;lt;lt;lt;lt;lt;lt; db.endTransaction(); } } };
Альтернативным решением было бы не использовать транзакцию, например
private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback() { @Override public void onCreate(@NonNull SupportSQLiteDatabase db) { super.onCreate(db); try { db.execSQL("INSERT OR IGNORE INTO ing_lists DEFAULT VALUES"); } catch (Exception e) { Log.d("TOM_TEST", e.toString()); } } };
В этом нет никакого недостатка, поскольку выполнение одной инструкции SQL само по себе является транзакцией.