首页 > 解决方案 > 我们可以在 android 中使用 sqlite3_backup 函数将内存数据库加载到持久存储吗?

问题描述

我想开发一个 android 应用程序,用于将条目存储在内存数据库中,然后在关闭应用程序时将它们复制到内部存储,并在再次打开应用程序时将数据加载回内存数据库。除了查询数据库和复制单个条目之外,还有什么方法可以使用sqlite3_backup_init()andsqlite3_backup_step()来做到这一点?

标签: androiddatabasesqlitein-memory

解决方案


我不相信访问底层 API 是一项微不足道的任务,所以答案可能是肯定的,但是。

不幸的是,Android 上的标准 SQLite 不可用的替代方法是使用VACUUM INTO (需要 SQLite 3.27.0 (如果我没记错的话))

但是,使用查询从内存备份到文件并不难,而且可能比您预期的要简单得多。

下面的代码是一个非常基础的,复制所有的表(除了那些以 sqlite_ 和 android_metadata 为前缀的表)。

public class MainActivity extends AppCompatActivity {

    SQLiteDatabase memorydb;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        memorydb = SQLiteDatabase.create(null);
        memorydb.execSQL("CREATE TABLE tablex (_id INTEGER PRIMARY KEY, name TEXT)");
        memorydb.execSQL("INSERT INTO tablex (name) VALUES ('FRED'),('MARY'),('JANE')");
        copyMemoryDBToFileDB();
        memorydb.close();

    }

    private void copyMemoryDBToFileDB() {
        String attachSchema = "diskdb";
        File db = new File(getDatabasePath("mydb").getPath());
        if (db.exists()) {
            db.delete();
        }
        if (!db.getParentFile().exists()) {
            db.getParentFile().mkdirs();
        }
        String dbFilePath = db.getPath();
        SQLiteDatabase dbFile = SQLiteDatabase.openDatabase(dbFilePath, null, SQLiteDatabase.CREATE_IF_NECESSARY);
        dbFile.setForeignKeyConstraintsEnabled(false);
        dbFile.close();
        memorydb.beginTransaction();
        memorydb.execSQL("ATTACH DATABASE '" + dbFilePath + "' AS " + attachSchema);
        //Cursor csr = memorydb.rawQuery("SELECT * FROM sqlite_master WHERE type=?",new String[]{"table"});
        Cursor csr = memorydb.query("main.sqlite_master", null, "type=?", new String[]{"table"}, null, null, null);
        while (csr.moveToNext()) {
            String currentTable = csr.getString(csr.getColumnIndex("name"));
            String dbfileTable = attachSchema + "." + currentTable;
            if (currentTable.startsWith("sqlite_") || currentTable.equals("android_metadata"))
                continue;

            String crtSQL = csr.getString(csr.getColumnIndex("sql"))
                    .replace(currentTable, dbfileTable).replace("CREATE TABLE ", "CREATE TABLE IF NOT EXISTS ");
            memorydb.execSQL(crtSQL);
            memorydb.execSQL("INSERT INTO " + dbfileTable + " SELECT * FROM " + currentTable);
        }
        memorydb.setTransactionSuccessful();
        memorydb.endTransaction();
        memorydb.execSQL("DETACH DATABASE " + attachSchema);
    }
}
  • 如果您有视图/触发器/索引,则可以扩展上述内容

    • 最好在复制数据之后执行这些操作,尤其是触发器和索引,因为您不希望触发器在数据之后重新触发和构建索引可以减少花费的时间。
  • 复制SQL基本上是INSERT INTO diskdb.the_current_tableName SELECT * FROM the_current_tableName(注意应该不用关闭外键支持,默认是关闭的)。

例子

上述运行的数据库文件已在 Navicat 中处理并打开:-

在此处输入图像描述

示例 2

此示例从 IN MEMORY DB 的现有副本复制到 IN MEMORY DB 并制作 IN MEMORY db 的磁盘副本,并具有三个表:-

作为活动的代码(因此上下文可用)

:-

public class MainActivity extends AppCompatActivity {

    private static final String ATTACH_DBFILE_SCHEMA = "diskdb";
    private static final String ATTACH_MASTER_SCHEMA = "main";
    private static final String ATTACH_SCHEMA_SEPERATOR = ".";
    private static final String SQLITE_MASTER_TABLE = "sqlite_master";
    private static final String SQLITE_MASTER_NAME_COLUMN = "name";
    private static final String SQLITE_MASTER_TYPE_COLUMN = "type";
    private static final String SQLITE_MASTER_SQL_COLUMN = "sql";
    private static final String SQLITE_MASTER_TYPE_TABLE = "table";
    private static final String SQLITE_SYSTEMDB_PREFIX = "sqlite_";
    private static final String ANDROID_METADATA = "android_metadata";
    public static final String DISKDBNAME = "mydb";
    SQLiteDatabase memorydb;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        loadOrCreateMemoryDB();
        //Add some testing data (each time App is run)
        memorydb.execSQL("INSERT INTO tablex (name) VALUES ('FRED@' || (datetime('now'))),('MARY'),('JANE')");
        memorydb.execSQL("INSERT INTO tabley (name) VALUES ('BERT@' || (datetime('now'))),('SUE'),('TOM')");
        memorydb.execSQL("INSERT INTO tablez (name) VALUES ('JOHN@' || (datetime('now'))),('ALEX'),('CHRISTINE')");

        //Done so save In Memory DB to file
        copyMemoryDBToFileDB();
        memorydb.close();
    }

    /**
     * Copy the IN MEMORY DB to file (assumes App's)
     */
    private void copyMemoryDBToFileDB() {
        File db = new File(getDatabasePath(DISKDBNAME).getPath()); //Path to the disk based backup
        // delete the file if it exists and also create directories (i.e. databases directory)
        if (db.exists()) {
            db.delete();
        }
        if (!db.getParentFile().exists()) {
            db.getParentFile().mkdirs();
        }

        //Initialise the file as an SQLite database (for Android so it has android_metadata)
        SQLiteDatabase dbFile = SQLiteDatabase.openDatabase(db.getPath(), null, SQLiteDatabase.CREATE_IF_NECESSARY);
        dbFile.setForeignKeyConstraintsEnabled(false); //<<<<<<<<<< not needed
        dbFile.close(); //<<<<<<<<<< close the database so it will be initialised but empty

        // Do everything in a single transaction
        memorydb.beginTransaction();

        // Attach the disk dataabse
        memorydb.execSQL("ATTACH DATABASE '" + db.getPath() + "' AS " + ATTACH_DBFILE_SCHEMA);

        //Get the tables in the memory DB from the sqlite_master table
        Cursor csr = memorydb.query(
                ATTACH_MASTER_SCHEMA + "." + SQLITE_MASTER_TABLE,
                null,
                SQLITE_MASTER_TYPE_COLUMN + "=?",
                new String[]{SQLITE_MASTER_TYPE_TABLE},
                null, null, null
        );
        //Iterate through the tables
        while (csr.moveToNext()) {

            //get the current table and the generate the table to save to in the disk based DB
            String currentTable = csr.getString(csr.getColumnIndex("name"));
            String dbfileTable = ATTACH_DBFILE_SCHEMA + ATTACH_SCHEMA_SEPERATOR + currentTable;

            //Skip system tables (probably ok to skip) and also android_metadata
            if (currentTable.startsWith(SQLITE_SYSTEMDB_PREFIX) || currentTable.equals(ANDROID_METADATA))
                continue;

            //If not a skipped table then create it on disk db, using schema (as attached) and then load it
            String crtSQL = csr.getString(csr.getColumnIndex(SQLITE_MASTER_SQL_COLUMN))
                    .replaceFirst(currentTable, dbfileTable).replaceFirst("CREATE TABLE ", "CREATE TABLE IF NOT EXISTS ");

            memorydb.execSQL(crtSQL);
            memorydb.execSQL("INSERT INTO " + dbfileTable + " SELECT * FROM " + currentTable);
        }
        csr.close();

        // All done so end the transaction (commit changes)
        memorydb.setTransactionSuccessful();
        memorydb.endTransaction();
        // No longer need to have the database attached and detach it
        memorydb.execSQL("DETACH DATABASE '" + ATTACH_DBFILE_SCHEMA + "'");
    }

    /**
     * If a disk database exists then load it into the IN MEMORY DB, else create the In MEMORY DB with tables
     */
    private void loadOrCreateMemoryDB() {

        // Get the File of the disck based DB
        File dbFile = new File(getDatabasePath(DISKDBNAME).getPath());

        // Create the In MEMORY DB
        memorydb = SQLiteDatabase.create(null);

        //If the disk based db exists copy the tables to the In MEMEORY DB
        if (dbFile.exists()) {

            // Attach the disk based db
            memorydb.execSQL("ATTACH DATABASE '" + dbFile.getPath() + "' AS " +ATTACH_DBFILE_SCHEMA);

            // Do everything in a single transaction
            memorydb.beginTransaction();

            // Get the tables in the disk based DB
            Cursor csr = memorydb.query(
                    ATTACH_DBFILE_SCHEMA + ATTACH_SCHEMA_SEPERATOR + SQLITE_MASTER_TABLE,
                    null,
                    SQLITE_MASTER_TYPE_COLUMN +"=?",
                    new String[]{SQLITE_MASTER_TYPE_TABLE},
                    null,null,null
            );

            // Iterate through the tables
            while (csr.moveToNext()) {
                //get the current table tfor the IN MEMORY DB and the table on disk (append Schema)
                String currentTable = csr.getString(csr.getColumnIndex(SQLITE_MASTER_NAME_COLUMN));
                String dbFileTable = ATTACH_DBFILE_SCHEMA + ATTACH_SCHEMA_SEPERATOR + currentTable;

                // Skip system tables and also android_metadata
                if (currentTable.startsWith(SQLITE_SYSTEMDB_PREFIX) || currentTable.equals(ANDROID_METADATA))
                    continue;

                // Otherwise create the table based upon the SQL from sqlite_master (should not need IF NOT EXISTS)
                memorydb.execSQL(csr.getString(csr.getColumnIndex(SQLITE_MASTER_SQL_COLUMN)));
                // Load the data extracted from the disk based table
                memorydb.execSQL("INSERT INTO main." + currentTable + " SELECT * FROM " + dbFileTable);
            }
            // Done with the Cursor
            csr.close();
            // All done so end the transaction (commit changes)
            memorydb.setTransactionSuccessful();
            memorydb.endTransaction();
            // detach the disk based database
            memorydb.execSQL("DETACH DATABASE '" + ATTACH_DBFILE_SCHEMA + "'");
        } else {
            // No datbase file so just create tables (as required)
            memorydb.execSQL("CREATE TABLE tablex (_id INTEGER PRIMARY KEY, name TEXT)");
            memorydb.execSQL("CREATE TABLE tabley (_id INTEGER PRIMARY KEY, name TEXT)");
            memorydb.execSQL("CREATE TABLE tablez (_id INTEGER PRIMARY KEY, name TEXT)");
        }
    }
}

结果

  • Navicat 的屏幕截图,运行 3 次后(所以每个表中有 9 行,显示的是 tablex)

在此处输入图像描述


推荐阅读