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 而不是因异常而失败。