sql >> データベース >  >> RDS >> SQLite

作成済みのデータベースをアセットからコピーできません

    既存の既存のデータベースを別のコピーに置き換えたいと仮定すると、いくつかの問題があります。

    あなたが直面している問題は、データベースが存在するため、コピーが続行されないことです。つまり、 checkDatabase() trueを返します。

    単にcopyDatabase()を呼び出す場合 そうすると、アプリが実行されるたびにデータベースがコピーされます。これは、ユーザーがデータベースを変更できる場合、非効率的で破壊的です。

    あなたがする必要があるのは、既存のデータベースが変更されたかどうかを確認するためにテストできるインジケーターを用意することです。さまざまな方法がありますが、最も可能性の高い/一般的な方法は、SQLite user_versionを利用することです。 。これは整数値であり、 onUpgradeを介して現在のデータベースを更新するために頻繁に使用されます メソッド。

    データベースを開く一環として、SQLiteOpenHelper(およびそのサブクラス)は、データベースに格納されているuser_versionを、指定されたバージョン番号(SQLiteOpenHelperスーパーコールの4番目のパラメーター)と比較し、後者が次に、onUpgradeメソッドが呼び出されます。 (逆の場合は、 onDowngrade メソッドが呼び出され、コード化されていないと例外が発生します。

    user_versionは、SQLite管理ツールユーザーでSQL PRAGMA user_version = nに設定できます。 。

    もう1つの問題は、Android 9から、データベースがデフォルトでWAL(先行書き込み)モードで開かれることです。 this.getReadableDatabase();を使用した上記のコード その結果、-shmファイルと-walファイルが作成されます。それらが存在すると、トラップされたエラーが発生し(コピーされたデータベースと一致しないため)、SQLiteOpenHelperが空の(理論的に使用可能なデータベース)を作成し、基本的にコピーされたデータベースをワイプします(これが発生すると思います> 。

    this.getReadableDatabase();の理由 使用されてきたのは、アプリデータがない場合にデータベースが発生するという問題を回避することです。 フォルダ/ディレクトリが存在せず、上記を使用して作成します。正しい方法は、データベースディレクトリ/フォルダが存在しない場合は作成することです。そのため、-walファイルと-shmファイルは作成されません。

    以下は、問題を克服し、user_versionの変更に基づいて既存のデータベースの変更されたバージョンをコピーできるようにするDatabseHelperの例です。

    public class DBHelperV001 extends SQLiteOpenHelper {
    
        public static final String DBNAME = "test.db"; //<<<<<<<<<< obviously change accordingly
    
        //
        private static int db_user_version, asset_user_version, user_version_offset = 60, user_version_length = 4;
        private static String stck_trc_msg = " (see stack-trace above)";
        private static String sqlite_ext_journal = "-journal";
        private static String sqlite_ext_shm = "-shm";
        private static String sqlite_ext_wal = "-wal";
        private static int copy_buffer_size = 1024 * 8; //Copy data in 8k chucks, change if wanted.
    
        SQLiteDatabase mDB;
    
        /**
         *  Instantiate the DBHelper, copying the databse from the asset folder if no DB exists
         *  or if the user_version is greater than the user_version of the current database.
         *  NOTE The pre-existing database copied into the assets folder MUST have the user version set
         *  to 1 or greater. If the user_version in the assets folder is increased above the
         *
         * @param context
         */
        public DBHelperV001(Context context) {
    
            // Note get the version according to the asset file
            // avoid having to maintain the version number passed
            super(context, DBNAME, null, setUserVersionFromAsset(context,DBNAME));
            if (!ifDbExists(context,DBNAME)) {
                copyDBFromAssets(context, DBNAME,DBNAME);
            } else {
                setUserVersionFromAsset(context,DBNAME);
                setUserVersionFromDB(context,DBNAME);
                if (asset_user_version > db_user_version) {
                    copyDBFromAssets(context,DBNAME,DBNAME);
                }
            }
            // Force open (and hence copy attempt) when constructing helper
            mDB = this.getWritableDatabase();
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        }
    
        @Override
        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        }
    
        /**
         * Check to see if the databse file exists
         * @param context   The Context
         * @param dbname    The databse name
         * @return          true id database file exists, else false
         */
        private static boolean ifDbExists(Context context, String dbname) {
            File db = context.getDatabasePath(dbname);
            if (db.exists()) return true;
            if (!db.getParentFile().exists()) {
                db.getParentFile().mkdirs();
            }
            return false;
        }
    
        /**
         * set the db_user_version according to the user_version obtained from the current database file
         * @param context   The Context
         * @param dbname    The database (file) name
         * @return          The user_version
         */
        private static int setUserVersionFromDB(Context context, String dbname) {
            File db = context.getDatabasePath(dbname);
            InputStream is;
            try {
                is = new FileInputStream(db);
            } catch (IOException e) {
                throw new RuntimeException("IOError Opening " + db.getPath() + " as an InputStream" + stck_trc_msg);
            }
            db_user_version = getUserVersion(is);
            Log.d("DATABASEUSERVERSION","Obtained user_version from current DB, it is " + String.valueOf(db_user_version)); //TODO remove for live App
            return db_user_version;
        }
    
        /**
         * set the asset_user_version according to the user_version from the asset file
         * @param context
         * @param assetname
         * @return
         */
        private static int setUserVersionFromAsset(Context context, String assetname) {
            InputStream is;
            try {
                is = context.getAssets().open(assetname);
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException("IOError Getting asset " + assetname + " as an InputStream" + stck_trc_msg);
            }
            asset_user_version = getUserVersion(is);
            Log.d("ASSETUSERVERSION","Obtained user_version from asset, it is " + String.valueOf(asset_user_version)); //TODO remove for Live App
            return asset_user_version;
        }
    
        /**
         * Retrieve SQLite user_version from the provied InputStream
         * @param is    The InputStream
         * @return      the user_version
         */
        private static int getUserVersion(InputStream is) {
            String ioerrmsg = "Reading DB header bytes(60-63) ";
            int rv;
            byte[] buffer = new byte[user_version_length];
            byte[] header = new byte[64];
            try {
                is.skip(user_version_offset);
                is.read(buffer,0,user_version_length);
                ByteBuffer bb = ByteBuffer.wrap(buffer);
                rv = ByteBuffer.wrap(buffer).getInt();
                ioerrmsg = "Closing DB ";
                is.close();
                return rv;
            } catch (IOException e) {
                e.printStackTrace();
                throw  new RuntimeException("IOError " + ioerrmsg + stck_trc_msg);
            }
        }
    
        /**
         * Copy the database file from the assets 
         * Note backup of existing files may not be required
         * @param context   The Context
         * @param dbname    The database (file)name
         * @param assetname The asset name (may therefore be different but )
         */
        private static void copyDBFromAssets(Context context, String dbname, String assetname) {
            String tag = "COPYDBFROMASSETS";
            Log.d(tag,"Copying Database from assets folder");
            String backup_base = "bkp_" + String.valueOf(System.currentTimeMillis());
            String ioerrmsg = "Opening Asset " + assetname;
    
            // Prepare Files that could be used
            File db = context.getDatabasePath(dbname);
            File dbjrn = new File(db.getPath() + sqlite_ext_journal);
            File dbwal = new File(db.getPath() + sqlite_ext_wal);
            File dbshm = new File(db.getPath() + sqlite_ext_shm);
            File dbbkp = new File(db.getPath() + backup_base);
            File dbjrnbkp = new File(db.getPath() + backup_base);
            File dbwalbkp = new File(db.getPath() + backup_base);
            File dbshmbkp = new File(db.getPath() + backup_base);
            byte[] buffer = new byte[copy_buffer_size];
            int bytes_read = 0;
            int total_bytes_read = 0;
            int total_bytes_written = 0;
    
            // Backup existing sqlite files
            if (db.exists()) {
                db.renameTo(dbbkp);
                dbjrn.renameTo(dbjrnbkp);
                dbwal.renameTo(dbwalbkp);
                dbshm.renameTo(dbshmbkp);
            }
            // ALWAYS delete the additional sqlite log files
            dbjrn.delete();
            dbwal.delete();
            dbshm.delete();
    
            //Attempt the copy
            try {
                ioerrmsg = "Open InputStream for Asset " + assetname;
                InputStream is = context.getAssets().open(assetname);
                ioerrmsg = "Open OutputStream for Databse " + db.getPath();
                OutputStream os = new FileOutputStream(db);
                ioerrmsg = "Read/Write Data";
                 while((bytes_read = is.read(buffer)) > 0) {
                     total_bytes_read = total_bytes_read + bytes_read;
                     os.write(buffer,0,bytes_read);
                     total_bytes_written = total_bytes_written + bytes_read;
                 }
                 ioerrmsg = "Flush Written data";
                 os.flush();
                 ioerrmsg = "Close DB OutputStream";
                 os.close();
                 ioerrmsg = "Close Asset InputStream";
                 is.close();
                 Log.d(tag,"Databsse copied from the assets folder. " + String.valueOf(total_bytes_written) + " bytes were copied.");
                 // Delete the backups
                 dbbkp.delete();
                 dbjrnbkp.delete();
                 dbwalbkp.delete();
                 dbshmbkp.delete();
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException("IOError attempting to " + ioerrmsg + stck_trc_msg);
            }
        }
    }
    

    使用例

    次のアセットファイル(sqliteデータベース)を検討してください(アプリであるため警告 ):-

    したがって、2つのデータベースがあります(PRAGMA user_version = 1を使用して設定されたuser_versionと同じバー およびPRAGMA user_version = 2 それぞれ/ファイル名による)新しい場合は、最初にアプリを実行して(つまり、アンインストールして)、ファイル test.dbV1 名前がtest.dbに変更されました 次のアクティビティが使用されます:-

    public class MainActivity extends AppCompatActivity {
    
        DBHelperV001 mDbhlpr;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mDbhlpr = new DBHelperV001(this);
            DatabaseUtils.dumpCursor(
                    mDbhlpr.getWritableDatabase().query(
                            "sqlite_master",
                            null,null,null,null,null,null
                    )
            );
        }
    }
    
    • これは、データベースヘルパー(データベースをコピーまたは使用する)をインスタンス化してから、sqlite_masterテーブルをダンプするだけです。

    ログには:-

    が含まれます
    04-02 12:55:36.258 644-644/aaa.so55441840 D/ASSETUSERVERSION: Obtained user_version from asset, it is 1
    04-02 12:55:36.258 644-644/aaa.so55441840 D/COPYDBFROMASSETS: Copying Database from assets folder
    04-02 12:55:36.262 644-644/aaa.so55441840 D/COPYDBFROMASSETS: Databsse copied from the assets folder. 69632 bytes were copied.
    04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out: >>>>> Dumping cursor [email protected]
    04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out: 0 {
    04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out:    type=table
    04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out:    name=android_metadata
    04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out:    tbl_name=android_metadata
    04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out:    rootpage=3
    04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
    04-02 12:55:36.266 644-644/aaa.so55441840 I/System.out: }
    04-02 12:55:36.266 644-644/aaa.so55441840 I/System.out: 1 {
    04-02 12:55:36.266 644-644/aaa.so55441840 I/System.out:    type=table
    04-02 12:55:36.266 644-644/aaa.so55441840 I/System.out:    name=shops
    ..........
    

    user_versionが2の新しいバージョンのDBが導入されたとき

    • つまり test.db これはtest.dbV1でした 名前がtest.dbV1に変更されました そして、
      • (事実上削除)
    • test.dbV2 その後、名前が test.dbに変更されます
      • (新しいアセットファイルを効果的に導入)次に:-

    その後、アプリが再実行され、ログに次の内容が含まれます:-

    04-02 13:04:25.044 758-758/? D/ASSETUSERVERSION: Obtained user_version from asset, it is 2
    04-02 13:04:25.046 758-758/? D/ASSETUSERVERSION: Obtained user_version from asset, it is 2
    04-02 13:04:25.046 758-758/? D/DATABASEUSERVERSION: Obtained user_version from current DB, it is 1
    04-02 13:04:25.047 758-758/? D/COPYDBFROMASSETS: Copying Database from assets folder
    04-02 13:04:25.048 758-758/? D/COPYDBFROMASSETS: Databsse copied from the assets folder. 69632 bytes were copied.
    04-02 13:04:25.051 758-758/? I/System.out: >>>>> Dumping cursor [email protected]
    04-02 13:04:25.052 758-758/? I/System.out: 0 {
    04-02 13:04:25.052 758-758/? I/System.out:    type=table
    04-02 13:04:25.052 758-758/? I/System.out:    name=android_metadata
    04-02 13:04:25.052 758-758/? I/System.out:    tbl_name=android_metadata
    04-02 13:04:25.052 758-758/? I/System.out:    rootpage=3
    04-02 13:04:25.052 758-758/? I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
    04-02 13:04:25.052 758-758/? I/System.out: }
    04-02 13:04:25.052 758-758/? I/System.out: 1 {
    04-02 13:04:25.052 758-758/? I/System.out:    type=table
    04-02 13:04:25.052 758-758/? I/System.out:    name=shops
    

    最後に、後続の実行、つまり更新されたアセットがない場合、ログには次のように表示されます:-

    04-02 13:05:50.197 840-840/aaa.so55441840 D/ASSETUSERVERSION: Obtained user_version from asset, it is 2
    04-02 13:05:50.198 840-840/aaa.so55441840 D/ASSETUSERVERSION: Obtained user_version from asset, it is 2
    04-02 13:05:50.198 840-840/aaa.so55441840 D/DATABASEUSERVERSION: Obtained user_version from current DB, it is 2
    04-02 13:05:50.201 840-840/aaa.so55441840 I/System.out: >>>>> Dumping cursor [email protected]
    04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out: 0 {
    04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    type=table
    04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    name=android_metadata
    04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    tbl_name=android_metadata
    04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    rootpage=3
    04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
    04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out: }
    04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out: 1 {
    04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    type=table
    04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    name=shops
    

    つまり、アセットは実質的に同じであるため、コピーは行われません




    1. SQL Serverでマテリアライズドビューを作成するにはどうすればよいですか?

    2. SQLServerパフォーマンス監視ツールを使用することでメリットが得られる3つの領域

    3. ActiveRecord_Associations_CollectionProxyのRails未定義メソッド

    4. SQLServerで結果セットのスキーマを取得する3つの方法