Android Room 可以在打开之前验证导入的数据库吗?
Can Android Room validate an imported database before it is opened?
问题: 在第一次查询之前,我似乎无法让导入的数据库在 Android 房间中失败?我想要 try/catch
并验证数据库,但在第一次查询之前我没有成功捕获它。我一直认为 Android Room 在 instance
创建时验证数据库并根据模式构建,但显然不是。所以数据库在第一次查询时失败。
我正在尝试做的事情: 此应用程序管理多个可以在用户之间共享的数据库。因此可以导入或导出数据库。我怀疑在某些时候,有人会尝试导入错误的数据库和/或结构或做一些导致它失败的事情。我正试图在 instance
/ 构建数据库或更早的时候捕获失败。
我试过的: 我在第一个实例创建时有一个 try/catch/finally
块,但它只在第一次查询时失败...然后它注意到缺少 table 或列。如果可能的话,我想早点抓住它。我查看了 RoomDatabase
方法,但除了让它中断之外,没有什么特别适用于我看到的验证。
I always thought Android Room validated the database at the moment of instance creation and build against the schema, but apparently not.
数据库验证是打开过程的一部分,直到您实际尝试访问数据库时才会发生,而不是在获取实例时发生。
I can't seem to get an imported database to fail in Android Room until first queried?
当您获取实例时,您可以通过实例的 openHelper 使用 getWritableDatabase 或 getReadableDatabase 获取(或尝试获取)SupportSQLiteDatabase 来强制打开。
例如
(科特林)
db = TheDatabase.getInstance(this)
try {
val supportDB = db.openHelper.writableDatabase
}
catch(e: Exception) {
....
}
(Java)
db = TheDatabase.getInstance(this);
try {
SupportSQLiteDatabase supportDB = db.getOpenHelper().getWritableDatabase();
}
catch (Exception e) {
....
}
备选方案 - 自我验证
您也可以进行自己的验证,从而避免异常(如果验证足够简单)。您也可以进行更正,从而允许可能的轻微违规行为被接受table.
在获取实际实例之前,您可以获取根据 Room 创建的验证实例(不同的数据库名称),然后自己比较架构。
这里有一个示例,旨在通过使用不同的 table 名称(不是 tablex 而不是 tableX)创建真实数据库来检测 table 缺失。
TableX实体:-
@Entity
class TableX {
@PrimaryKey
Long id=null;
String name;
String other;
}
没有 Dao,因为示例不需要。
带有获取实例方法的TheDatabase,一个用于正常,另一个用于获取另一个(验证(用于模式比较的空模型))数据库但作为SQLiteDatabase。
@Database(entities = {TableX.class},version = 1)
abstract class TheDatabase extends RoomDatabase {
private static volatile TheDatabase instance = null;
private static volatile TheDatabase validationInstance = null;
static TheDatabase getInstance(Context context, String databaseName) {
if (instance == null ) {
instance = Room.databaseBuilder(context,TheDatabase.class,databaseName)
.allowMainThreadQueries()
.build();
}
return instance;
}
static SQLiteDatabase getValidationInstance(Context context, String databaseName) {
// Delete DB if it exists
if (context.getDatabasePath(databaseName).exists()) {
context.getDatabasePath(databaseName).delete();
}
// Create database and close it
TheDatabase db = Room.databaseBuilder(context,TheDatabase.class,databaseName)
.allowMainThreadQueries()
.build();
db.getOpenHelper().getWritableDatabase();
db.close();
return SQLiteDatabase.openDatabase(context.getDatabasePath(databaseName).getPath(),null,SQLiteDatabase.OPEN_READWRITE);
}
}
- 请注意,这会强制打开创建 model/validation 数据库(否则 openDatabase 将失败)。
最后是演示 MainActivity,它创建了一个无效的数据库,然后继续进行简单的验证(只需检查预期的 table 是否存在)。如果房间预期的 table 存在,则只打开(在本例中从不打开)数据库。
public class MainActivity extends AppCompatActivity {
public static final String DATABASE_NAME = "the_database.db";
public static final String VALIDATION_DATABASE_NAME = DATABASE_NAME + "_validation";
public static final String TAG = "DBVALIDATION";
TheDatabase db;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
createIncorrectDatabase(this,DATABASE_NAME);
if (validateDatabase(VALIDATION_DATABASE_NAME,DATABASE_NAME) < 0) {
Log.d(TAG,"DATABASE " + DATABASE_NAME + " does mot match model.");
} else {
/* Database validated OK so use it */
db = TheDatabase.getInstance(this,DATABASE_NAME);
}
}
/* Purposefully create invalid database */
private void createIncorrectDatabase(Context context, String databaseName) {
File dbfile = context.getDatabasePath(databaseName);
if (!dbfile.exists()) {
dbfile.getParentFile().mkdirs();
}
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(context.getDatabasePath(databaseName),null);
db.execSQL("CREATE TABLE IF NOT EXISTS nottablex(id INTEGER PRIMARY KEY,name TEXT)");
db.close();
}
@SuppressLint("Range")
private long validateDatabase(String modelDatabase, String actualDatabase) {
String sqlite_master = "sqlite_master";
/* Want to skip room_master_table and sqlite tables susch as sqlite_sequence */
/* in this example only checking tables to show the basic technique */
String wherecluase = "name NOT LIKE 'sqlite_%' AND name NOT LIKE 'room_%' AND type = 'table'";
long rv = 0;
/* Get the model/validation database */
SQLiteDatabase modelDB = TheDatabase.getValidationInstance(this,modelDatabase);
/* Only need to check if the database exists as otherwise it will be created according to Room */
if (this.getDatabasePath(actualDatabase).exists()) {
/* Open as an SQLiteDatabase so no Room open to throw an exception */
SQLiteDatabase actualDB = SQLiteDatabase.openDatabase(this.getDatabasePath(actualDatabase).getAbsolutePath(),null,SQLiteDatabase.OPEN_READWRITE);
/* Get the tables expected from the model Room database */
Cursor modelTableNames = modelDB.query(sqlite_master,null,wherecluase,null,null,null,null);
Cursor actualTableNames = null; /* prepare Cursor */
/* Loop through the tables names in the model checking if they exist */
while (modelTableNames.moveToNext()) {
/* See if the expected table exists */
actualTableNames = actualDB.query(sqlite_master,null,"name=?",new String[]{modelTableNames.getString(modelTableNames.getColumnIndex("name"))},null,null,null);
if (!actualTableNames.moveToFirst()) {
Log.d(TAG,"Table " + modelTableNames.getString(modelTableNames.getColumnIndex("name")) + " not found.");
rv = rv -1; /* Table not found so decrement rv to indicate number not found */
}
}
/* Close the actualTableNames Cursor if it was used */
if (actualTableNames != null) {
actualTableNames.close();
}
/* Close the modelTableNames Cursor */
modelTableNames.close();
/* Close the actual database so Room can use it (comment out to show results in database Inspector)*/
actualDB.close();
} else {
Log.d(TAG,"Actual Database " + actualDatabase + " does not exist. No validation required as it would be created");
}
/* Close and delete the model database (comment out to show results in database Inspector)*/
modelDB.close();
this.getDatabasePath(modelDatabase).delete();
return rv;
}
}
结果
日志包括:-
D/DBVALIDATION: Table TableX not found.
D/DBVALIDATION: DATABASE the_database.db does mot match model.
绕过关闭和模型删除上面的数据库是:-
- 请注意,在这个简单的示例中,Room 实际上会创建 TableX table 而不是因异常而失败。
问题: 在第一次查询之前,我似乎无法让导入的数据库在 Android 房间中失败?我想要 try/catch
并验证数据库,但在第一次查询之前我没有成功捕获它。我一直认为 Android Room 在 instance
创建时验证数据库并根据模式构建,但显然不是。所以数据库在第一次查询时失败。
我正在尝试做的事情: 此应用程序管理多个可以在用户之间共享的数据库。因此可以导入或导出数据库。我怀疑在某些时候,有人会尝试导入错误的数据库和/或结构或做一些导致它失败的事情。我正试图在 instance
/ 构建数据库或更早的时候捕获失败。
我试过的: 我在第一个实例创建时有一个 try/catch/finally
块,但它只在第一次查询时失败...然后它注意到缺少 table 或列。如果可能的话,我想早点抓住它。我查看了 RoomDatabase
方法,但除了让它中断之外,没有什么特别适用于我看到的验证。
I always thought Android Room validated the database at the moment of instance creation and build against the schema, but apparently not.
数据库验证是打开过程的一部分,直到您实际尝试访问数据库时才会发生,而不是在获取实例时发生。
I can't seem to get an imported database to fail in Android Room until first queried?
当您获取实例时,您可以通过实例的 openHelper 使用 getWritableDatabase 或 getReadableDatabase 获取(或尝试获取)SupportSQLiteDatabase 来强制打开。
例如
(科特林)
db = TheDatabase.getInstance(this)
try {
val supportDB = db.openHelper.writableDatabase
}
catch(e: Exception) {
....
}
(Java)
db = TheDatabase.getInstance(this);
try {
SupportSQLiteDatabase supportDB = db.getOpenHelper().getWritableDatabase();
}
catch (Exception e) {
....
}
备选方案 - 自我验证
您也可以进行自己的验证,从而避免异常(如果验证足够简单)。您也可以进行更正,从而允许可能的轻微违规行为被接受table.
在获取实际实例之前,您可以获取根据 Room 创建的验证实例(不同的数据库名称),然后自己比较架构。
这里有一个示例,旨在通过使用不同的 table 名称(不是 tablex 而不是 tableX)创建真实数据库来检测 table 缺失。
TableX实体:-
@Entity
class TableX {
@PrimaryKey
Long id=null;
String name;
String other;
}
没有 Dao,因为示例不需要。
带有获取实例方法的TheDatabase,一个用于正常,另一个用于获取另一个(验证(用于模式比较的空模型))数据库但作为SQLiteDatabase。
@Database(entities = {TableX.class},version = 1)
abstract class TheDatabase extends RoomDatabase {
private static volatile TheDatabase instance = null;
private static volatile TheDatabase validationInstance = null;
static TheDatabase getInstance(Context context, String databaseName) {
if (instance == null ) {
instance = Room.databaseBuilder(context,TheDatabase.class,databaseName)
.allowMainThreadQueries()
.build();
}
return instance;
}
static SQLiteDatabase getValidationInstance(Context context, String databaseName) {
// Delete DB if it exists
if (context.getDatabasePath(databaseName).exists()) {
context.getDatabasePath(databaseName).delete();
}
// Create database and close it
TheDatabase db = Room.databaseBuilder(context,TheDatabase.class,databaseName)
.allowMainThreadQueries()
.build();
db.getOpenHelper().getWritableDatabase();
db.close();
return SQLiteDatabase.openDatabase(context.getDatabasePath(databaseName).getPath(),null,SQLiteDatabase.OPEN_READWRITE);
}
}
- 请注意,这会强制打开创建 model/validation 数据库(否则 openDatabase 将失败)。
最后是演示 MainActivity,它创建了一个无效的数据库,然后继续进行简单的验证(只需检查预期的 table 是否存在)。如果房间预期的 table 存在,则只打开(在本例中从不打开)数据库。
public class MainActivity extends AppCompatActivity {
public static final String DATABASE_NAME = "the_database.db";
public static final String VALIDATION_DATABASE_NAME = DATABASE_NAME + "_validation";
public static final String TAG = "DBVALIDATION";
TheDatabase db;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
createIncorrectDatabase(this,DATABASE_NAME);
if (validateDatabase(VALIDATION_DATABASE_NAME,DATABASE_NAME) < 0) {
Log.d(TAG,"DATABASE " + DATABASE_NAME + " does mot match model.");
} else {
/* Database validated OK so use it */
db = TheDatabase.getInstance(this,DATABASE_NAME);
}
}
/* Purposefully create invalid database */
private void createIncorrectDatabase(Context context, String databaseName) {
File dbfile = context.getDatabasePath(databaseName);
if (!dbfile.exists()) {
dbfile.getParentFile().mkdirs();
}
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(context.getDatabasePath(databaseName),null);
db.execSQL("CREATE TABLE IF NOT EXISTS nottablex(id INTEGER PRIMARY KEY,name TEXT)");
db.close();
}
@SuppressLint("Range")
private long validateDatabase(String modelDatabase, String actualDatabase) {
String sqlite_master = "sqlite_master";
/* Want to skip room_master_table and sqlite tables susch as sqlite_sequence */
/* in this example only checking tables to show the basic technique */
String wherecluase = "name NOT LIKE 'sqlite_%' AND name NOT LIKE 'room_%' AND type = 'table'";
long rv = 0;
/* Get the model/validation database */
SQLiteDatabase modelDB = TheDatabase.getValidationInstance(this,modelDatabase);
/* Only need to check if the database exists as otherwise it will be created according to Room */
if (this.getDatabasePath(actualDatabase).exists()) {
/* Open as an SQLiteDatabase so no Room open to throw an exception */
SQLiteDatabase actualDB = SQLiteDatabase.openDatabase(this.getDatabasePath(actualDatabase).getAbsolutePath(),null,SQLiteDatabase.OPEN_READWRITE);
/* Get the tables expected from the model Room database */
Cursor modelTableNames = modelDB.query(sqlite_master,null,wherecluase,null,null,null,null);
Cursor actualTableNames = null; /* prepare Cursor */
/* Loop through the tables names in the model checking if they exist */
while (modelTableNames.moveToNext()) {
/* See if the expected table exists */
actualTableNames = actualDB.query(sqlite_master,null,"name=?",new String[]{modelTableNames.getString(modelTableNames.getColumnIndex("name"))},null,null,null);
if (!actualTableNames.moveToFirst()) {
Log.d(TAG,"Table " + modelTableNames.getString(modelTableNames.getColumnIndex("name")) + " not found.");
rv = rv -1; /* Table not found so decrement rv to indicate number not found */
}
}
/* Close the actualTableNames Cursor if it was used */
if (actualTableNames != null) {
actualTableNames.close();
}
/* Close the modelTableNames Cursor */
modelTableNames.close();
/* Close the actual database so Room can use it (comment out to show results in database Inspector)*/
actualDB.close();
} else {
Log.d(TAG,"Actual Database " + actualDatabase + " does not exist. No validation required as it would be created");
}
/* Close and delete the model database (comment out to show results in database Inspector)*/
modelDB.close();
this.getDatabasePath(modelDatabase).delete();
return rv;
}
}
结果
日志包括:-
D/DBVALIDATION: Table TableX not found.
D/DBVALIDATION: DATABASE the_database.db does mot match model.
绕过关闭和模型删除上面的数据库是:-
- 请注意,在这个简单的示例中,Room 实际上会创建 TableX table 而不是因异常而失败。