尽管未使用 createFromFile/Asset,但预打包数据库的架构无效
Pre-packaged database has an invalid schema DESPITE not using createFromFile/Asset
我一直在到处寻找这个错误的原因:
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.call(IngListItemDao_Impl.java:1036)
at com.example.shoppinglistapp2.db.dao.IngListItemDao_Impl.call(IngListItemDao_Impl.java:1033)
at androidx.room.RoomTrackingLiveData.run(RoomTrackingLiveData.java:90)
每个其他类似问题似乎至少有一些 table 元素,或者正在使用 databaseBuilder.createFromAsset 功能。然而我不是,我的数据库代码粘贴在下面。我试过删除所有迁移,然后退回到破坏性迁移。我查看了导出的架构文件,这些文件清楚地正确显示了所有属性。在我的应用程序中一切正常,直到我尝试将它安装在没有现有数据库实例的新设备上。我可以 运行 在具有来自应用程序之前 builds/use 的现有数据库数据的设备上正常运行,但是全新安装在启动时立即崩溃,并且出现上述错误。我之前没有尝试过这个,所以我不知道这个问题可能是在什么时候引入的。
我使用的是 Room 版本“2.4.0-alpha01”,但我也试过回到 2.3.0,没有任何区别。
是否有一些外部库试图预填充我不知道的内容?我不确定它甚至在哪里找到空 tables 出现在“已找到”部分中的架构 - 我什至没有它可能试图获取的 /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...
SlaDatabase 版本 17 的导出模式
{
"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')"
]
}
}
您将在未使用 setTransactionSuccessful
的情况下结束交易。如果没有,endTransaction
将回滚更改,有效地撤消更改,包括创建 Room 已创建的 tables。
因此,由于回滚,第一个 table 检查没有发现(配方)table 与预期的(配方)table 匹配,因此 找到 不包含列等
- 它说 预打包 数据库因为 Room 似乎假设这个不一致的数据库一定来自其他地方(它不是会创建什么房间);因此它有点误导(但它应该说什么?(修辞))。
您需要 use/apply 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(); //<<<<<<<<<<
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 语句本身就是一个事务。
我一直在到处寻找这个错误的原因:
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.call(IngListItemDao_Impl.java:1036)
at com.example.shoppinglistapp2.db.dao.IngListItemDao_Impl.call(IngListItemDao_Impl.java:1033)
at androidx.room.RoomTrackingLiveData.run(RoomTrackingLiveData.java:90)
每个其他类似问题似乎至少有一些 table 元素,或者正在使用 databaseBuilder.createFromAsset 功能。然而我不是,我的数据库代码粘贴在下面。我试过删除所有迁移,然后退回到破坏性迁移。我查看了导出的架构文件,这些文件清楚地正确显示了所有属性。在我的应用程序中一切正常,直到我尝试将它安装在没有现有数据库实例的新设备上。我可以 运行 在具有来自应用程序之前 builds/use 的现有数据库数据的设备上正常运行,但是全新安装在启动时立即崩溃,并且出现上述错误。我之前没有尝试过这个,所以我不知道这个问题可能是在什么时候引入的。
我使用的是 Room 版本“2.4.0-alpha01”,但我也试过回到 2.3.0,没有任何区别。
是否有一些外部库试图预填充我不知道的内容?我不确定它甚至在哪里找到空 tables 出现在“已找到”部分中的架构 - 我什至没有它可能试图获取的 /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...
SlaDatabase 版本 17 的导出模式
{
"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')"
]
}
}
您将在未使用 setTransactionSuccessful
的情况下结束交易。如果没有,endTransaction
将回滚更改,有效地撤消更改,包括创建 Room 已创建的 tables。
因此,由于回滚,第一个 table 检查没有发现(配方)table 与预期的(配方)table 匹配,因此 找到 不包含列等
- 它说 预打包 数据库因为 Room 似乎假设这个不一致的数据库一定来自其他地方(它不是会创建什么房间);因此它有点误导(但它应该说什么?(修辞))。
您需要 use/apply 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(); //<<<<<<<<<<
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 语句本身就是一个事务。