sql >> Databáze >  >> RDS >> SQLite

Nelze zkopírovat předem vytvořenou db z aktiv

Máte řadu problémů, za předpokladu, že chcete nahradit existující již existující databázi jinou kopií.

Problém, kterému čelíte, je ten, že pokud existuje databáze, kopírování nebude pokračovat, tj. checkDatabase() vrátí true.

Pokud byste měli jednoduše vyvolat copyDatabase() databáze by pak byla zkopírována při každém spuštění aplikace, což by bylo neefektivní a destruktivní, pokud by databázi mohl uživatel upravit.

Co musíte udělat, je mít indikátor, který lze otestovat, abyste zjistili, zda již existující databáze byla změněna. Existují různé způsoby, ale nejpravděpodobnějším/nejběžnějším způsobem by bylo použití SQLite user_version . Toto je celočíselná hodnota a často se používá k aktualizaci aktuální databáze pomocí onUpgrade metoda.

V rámci otevření databáze SQLiteOpenHelper (a tedy její podtřída) porovnává user_version uloženou v databázi s dodaným číslem verze (4. parametr super volání SQLiteOpenHelper) a pokud je toto číslo větší než hodnota uložená v databáze, pak se zavolá metoda onUpgrade. (pokud je to naopak, pak onDowngrade bude zavolána metoda a bez jejího zakódování dojde k výjimce).

User_version lze nastavit v nástroji pro správu SQLite uživatele SQL PRAGMA user_version = n .

Dalším problémem je, že od Androidu 9 se databáze ve výchozím nastavení otevírá v režimu WAL (Write-Ahead Logging). Výše uvedený kód pomocí this.getReadableDatabase(); výsledkem jsou soubory -shm a -wal. Jejich existence má za následek zachycenou chybu (protože se pak neshodují se zkopírovanou databází), která pak vede k tomu, že SQLiteOpenHelper vytvoří prázdnou (teoreticky použitelnou databázi) a v podstatě vymaže zkopírovanou databázi (Věřím, že se to stane ).

Důvod, proč this.getReadableDatabase(); bylo použito je, že se vyřeší problém, že když nejsou k dispozici žádná data aplikací, databáze složka/adresář neexistuje a pomocí výše uvedeného se vytvoří. Správný způsob je vytvořit adresář/složku databází, pokud neexistuje. Soubory -wal a -shm jako takové nejsou vytvořeny.

Následuje příklad DatabseHelper, který překonává problémy a navíc umožňuje kopírování upravených verzí již existující databáze na základě změny user_version.

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);
        }
    }
}

Příklad použití

Zvažte následující soubory aktiv (databáze sqlite) (upozornění, protože aplikace by selhala ):-

Existují tedy dvě databáze (identický pruh user_version, jak je nastaven pomocí PRAGMA user_version = 1 a PRAGMA user_version = 2 respektive/podle názvů souborů)U zcela nového spusťte poprvé aplikaci (tj. odinstalujte) a poté soubor test.dbV1 je přejmenován na test.db a použije se následující aktivita :-

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
                )
        );
    }
}
  • To jednoduše vytvoří instanci Database Helper (který zkopíruje nebo použije databázi) a poté vypíše tabulku sqlite_master.

Protokol obsahuje :-

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
..........

Když je představena nová verze DB, která má user_version 2

  • tj. test.db což bylo test.dbV1 je přejmenován na test.dbV1 A pak
    • (účinně jej smaže)
  • test.dbV2 se poté přejmenuje na test.db
    • (účinné zavedení nového souboru aktiv) a poté :-

Aplikace se poté znovu spustí a protokol obsahuje :-

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

A konečně, s následným spuštěním, tj. bez aktualizovaného aktiva, protokol ukazuje:-

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

tj. žádná kopie vytvořená, protože aktivum je ve skutečnosti stejné




  1. Potřebujete vždy databázi pro svou aplikaci?

  2. PostgreSQL:role nemá oprávnění se přihlásit

  3. Entity framework PostgreSQL

  4. Jak TAN() funguje v MariaDB