Инициализация базы данных при первом запуске - PullRequest
0 голосов
/ 09 июня 2019

Я хотел бы инициализировать базу данных приложения, когда пользователь запускает приложение в первый раз.

Моей первой идеей было добавить файл базы данных в ресурсы и скопировать его в каталог app db. Но это не представляется возможным.

Итак, моя новая идея - закодировать все операторы вставки определенным методом. Но я не могу найти, как экспортировать всю фактическую базу данных в операторы вставки.

Я использую DB Navigator, и лучшее, что я нашел, это экспорт в csv, таблица за таблицей.

Не могу поверить, что нет лучшего и более простого способа ...

Ответы [ 2 ]

1 голос
/ 10 июня 2019

Хотя ответ, который предлагает использовать: -

//in a class which extends SQLiteOpenHelper
   public void importDefault() throws Exception {

        String assetPath = "db/myApp.default.db";
        String deviceDbDirectory;
        InputStream is;

        is = contexte.getAssets().open(assetPath);
        deviceDbDirectory = getWritableDatabase().getPath();
        writeExtractedFileToDisk(is, new FileOutputStream(deviceDbDirectory));
    }


    private void writeExtractedFileToDisk(InputStream in, OutputStream outs) throws IOException {
        byte[] buffer = new byte[1024];
        int length;
        while ((length = in.read(buffer))>0){
            outs.write(buffer, 0, length);
        }
        outs.flush();
        outs.close();
        in.close();
    }

Может работать для предварительного Android Pie, где используется режим по умолчанию journal . Он не будет работать без модификации для Android Pie +, где по умолчанию используется WAL ( W обряд- A head L ogging) .

Причина в том, что getWritableDatabase делает это, она получает (открывает) базу данных, что в режиме WAL приводит к генерации и заполнению файлов -shm и -wal.

Поскольку данные в файлах не фиксируются, когда копируемая база данных открывается, первое открытие после того, как открытие копии обнаруживает, что -wal и -shm не принадлежат скопированной базе данных (вместо этого они принадлежат пустой базе данных). В результате открытая база данных является новой пустой базой данных (из-за повреждения и того, как open пытается вернуть работоспособную базу данных).

Исправления

Есть ряд исправлений.

Один из них - переопределить метод onConfigure и использовать метод SQLiteDatabase db.disableWriteAheadLogging (); для использования режима журнала. например : -

@Override
public void onConfigure(SQLiteDatabase db) {
    super.onConfigure(db);
    db.disableWriteAheadLogging();
}
  • Приложение не использует преимущества, предлагаемые WAL, и поэтому не рекомендуется.

Другой способ - обеспечить фиксацию невыполненных транзакций. Этого можно достичь, закрыв базу данных перед выполнением копии. например используя: -

public void importDefault() throws Exception {

    String assetPath = "db/myApp.default.db";
    String deviceDbDirectory;
    SQLiteDatabase crtdirdb;
    InputStream is;

    is = contexte.getAssets().open(assetPath);
    deviceDbDirectory = (crtdirdb = getWritableDatabase()).getPath(); //CHANGED TO GET THE SQLITE DATABASE OBJECT
    crtdirdb.close(); //ADDED TO CLOSE THE DATABASE AND THUS COMMIT TRANSACTIONS

    writeExtractedFileToDisk(is, new FileOutputStream(deviceDbDirectory));
}
  • Преимущество заключается в том, что он будет работать как с pre, так и с Android Pie +, поддерживая заданный по умолчанию или заданный режим ведения журнала.

  • Однако, как и любой метод, который использует getWritableDatabase или getReadableDatabase (не имеет значения, какой из них последний получит первое время большую часть времени), это относительно ресурс голодный и поэтому не рекомендуется из-за его эффективности.

Еще одно исправление - удаление файлов -shm и - wal, что эффективно отбрасывает невыполненные транзакции.

  • Это все еще не рекомендуется, поскольку оно все еще неэффективно.

Зачем получать ???? База данных

getWritableDatabase или getReadableDatabase , по-видимому, использовались исторически просто для обхода проблемы ENOENT, с которой встречаются без.

ENONENT заключается в том, что при установке нового приложения и последующем запуске каталог data / data / package существует, но НЕ каталог data / data / package / database, и, следовательно, копирование файла завершается неудачно из-за отсутствующего каталога.

Обработка lazy / easy - это одна строка кода (но много основных строк для запуска) getWritableDatabse. Базовый код должен проверить, существует ли файл, и если он не проверяет, существуют ли родительские каталоги, и если их нет, откройте файл через открытый файл (назад) в открытом пакете SQlite SDK, который затем проходит через процесс создания заголовка файла, таблицы sqlite_master и таблицы android_metadata перед завершением работы с файлом -jorunal или файлами -wal и -shm, а затем с возвратом.

Вся эта обработка затем теряется / отменяется путем перезаписи базы данных копией из файла активов.

Эффективным способом проверки является использование File методов, чтобы увидеть, существует ли каталог database (не совсем так, как теоретически может измениться), и создать его с помощью Файл mkdirs метод.

Таким образом, метод importDefault был бы более эффективным, работал бы для Android Pie + и был бы более устойчивым в будущем, если бы он был: -

public void importDefault() throws Exception {

    String assetPath = "db" + File.separator + dbname;
    InputStream is = contexte.getAssets().open(assetPath);
    File db = contexte.getDatabasePath(dbname);
    if (!db.getParentFile().exists()) {
        db.getParentFile().mkdirs();
    }
    writeExtractedFileToDisk(is, new FileOutputStream(db));
} 
  • note dbname устанавливается как переменная класса для класса.

  • База данных имеет следующие таблицы: -

    • enter image description here
  • И следующие триггеры: -

    • enter image description here

Таким образом, полный класс, который переопределяет SQLiteOpenHelper, может быть: -

public class DBBasicAssetCopy extends SQLiteOpenHelper {

    Context contexte;
    static String dbname = "myApp.default.db";
    SQLiteDatabase mDB;
    public DBBasicAssetCopy(@Nullable Context context) {
        super(context, dbname, null , 1);
        this.contexte = context;
        if (!(new File(contexte.getDatabasePath(dbname).getPath())).exists()) {
            try {
                importDefault();
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("Error copying Asset");
            }
        }
        mDB = this.getWritableDatabase();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    /* Could be used as a fix
    @Override
    public void onConfigure(SQLiteDatabase db) {
        super.onConfigure(db);
        db.disableWriteAheadLogging();
    }
    */

    public void importDefault() throws Exception {

        String assetPath = "db" + File.separator + dbname; //minimal hard coding
        InputStream is = contexte.getAssets().open(assetPath);
        File db = contexte.getDatabasePath(dbname);
        if (!db.getParentFile().exists()) {
            db.getParentFile().mkdirs();
        }
        writeExtractedFileToDisk(is, new FileOutputStream(db));
    }

    private void writeExtractedFileToDisk(InputStream in, OutputStream outs) throws IOException {
        byte[] buffer = new byte[1024];
        int length;
        int totalbytes = 0;
        while ((length = in.read(buffer))>0){
            outs.write(buffer, 0, length);
            totalbytes = totalbytes + length;
        }
        outs.flush();
        outs.close();
        in.close();
        Log.d("WEFTD_TOTAL","The total bytes copied was " + String.valueOf(totalbytes));
    }
}

Другое исправление

Пример (подтверждение)

с использованием (БД в качестве актива): -

enter image description here

Вместе с указанным выше DBBasicAssetCopy классом и использованием: -

public class MainActivity extends AppCompatActivity {

    //DBAssetHelper mDBhlpr;
    DBBasicAssetCopy mDBHlpr002;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        /*
        mDBhlpr = new DBAssetHelper(this);
        Cursor csr = mDBhlpr.getWritableDatabase().query("sqlite_master",null,null,null,null,null,null);
        while (csr.moveToNext()) {
            Log.d("DBENTITIES","Name = " + csr.getString(csr.getColumnIndex("name"))
                    + "Type = " + csr.getString(csr.getColumnIndex("type")) +
                    "SQL = " + csr.getString(csr.getColumnIndex("sql")));
        }
        mDBhlpr.close();
        */

        mDBHlpr002 = new DBBasicAssetCopy(this);
        Cursor csr = mDBHlpr002.getWritableDatabase().query("sqlite_master",null,null,null,null,null,null);
        while (csr.moveToNext()) {
            Log.d("DBENTITIES","Name = " + csr.getString(csr.getColumnIndex("name"))
                    + "Type = " + csr.getString(csr.getColumnIndex("type")) +
                    "SQL = " + csr.getString(csr.getColumnIndex("sql")));
        }

        csr.close();
        mDBHlpr002.close();
    }
}

Прогон 1

с использованием кода (согласно другому ответу) на устройстве Android 23 API (эмулятор). Новая установка, которая использует: -

public void importDefault () создает исключение {

    String assetPath = "db/myApp.default.db";
    String deviceDbDirectory;
    InputStream is;

    is = contexte.getAssets().open(assetPath);
    deviceDbDirectory = getWritableDatabase().getPath();
    writeExtractedFileToDisk(is, new FileOutputStream(deviceDbDirectory));
}

Работает (тот же вывод, что и в 3 и 4)

Прогон 2 - ПРОБЛЕМА

используя код как для Запустите 1 на устройстве Android 28 API (эмулятор). новая установка: -

2019-06-10 12:35:21.566 13285-13285/aso.sqliteassethelpertest D/WEFTD_TOTAL: The total bytes copied was 40960
2019-06-10 12:35:21.566 13285-13285/aso.sqliteassethelpertest D/DBENTITIES: Name = android_metadataType = tableSQL = CREATE TABLE android_metadata (locale TEXT)
  • Да, хотя база данных была скопирована (скопировано около 40 КБ), единственная существующая таблица - это android_metadata .

Пробег 3

Запуск из новой установки на устройстве Android API 23 (эмулятор): -

2019-06-10 12:08:23.842 12981-12981/aso.sqliteassethelpertest D/WEFTD_TOTAL: The total bytes copied was 40960
2019-06-10 12:08:23.847 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = android_metadataType = tableSQL = CREATE TABLE android_metadata (locale TEXT)
2019-06-10 12:08:23.847 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = room_master_tableType = tableSQL = CREATE TABLE room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)
2019-06-10 12:08:23.847 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = EntityTestTableType = tableSQL = CREATE TABLE `EntityTestTable` (`_id` INTEGER NOT NULL, `booleanPrimative` INTEGER NOT NULL, `charPrimative` INTEGER NOT NULL, `bytePrimative` INTEGER NOT NULL, `shortPrimative` INTEGER NOT NULL, `intPrimative` INTEGER NOT NULL, `longPrimative` INTEGER NOT NULL, `doublePrimative` REAL NOT NULL, `floatPrimative` REAL NOT NULL, `stringObj` TEXT, `booleanObj` INTEGER, `characterObj` INTEGER, `byteObj` INTEGER, `shortObj` INTEGER, `intObj` INTEGER, `longObj` INTEGER, `floatObj` REAL, `doubleObj` REAL, PRIMARY KEY(`_id`))
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = EVENT_SETTINGSType = tableSQL = CREATE TABLE EVENT_SETTINGS (
        DBTS BIGINT NOT NULL,
        DBTS_TS TIMESTAMP NOT NULL
    )
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = CS_EVENT_PRICE_LISTType = tableSQL = CREATE TABLE CS_EVENT_PRICE_LIST (
        EVENT_PRICE_LIST_ID INTEGER        PRIMARY KEY AUTOINCREMENT,
        DESCRIPTION         VARCHAR (50)   NOT NULL,
        SALES_TAX           DECIMAL (8, 2) DEFAULT 0 NOT NULL,
        TAX_TYPE            INTEGER        DEFAULT 1 NOT NULL,
        INSERTEDBY          VARCHAR (50)   NOT NULL,
        INSTERTEDON         TIMESTAMP      NOT NULL,
        UPDATEDBY           VARCHAR (50)   NOT NULL,
        UPDATEDON           TIMESTAMP      NOT NULL,
        TS                  BIGINT         NOT NULL
    )
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = sqlite_sequenceType = tableSQL = CREATE TABLE sqlite_sequence(name,seq)
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = ti_CS_EVENT_PRICE_LIST_standardType = triggerSQL = CREATE TRIGGER ti_CS_EVENT_PRICE_LIST_standard BEFORE INSERT ON CS_EVENT_PRICE_LIST
    WHEN (SELECT Enabled FROM TriggerControl WHERE TriggerType = 'productTables')
        BEGIN 
        INSERT INTO CS_EVENT_PRICE_LIST ( Description, Sales_Tax, Tax_Type, insertedby, instertedon, updatedby, updatedon, ts)
            VALUES (new.Description, new.Sales_Tax, new.Tax_Type,
                    new.insertedby, 
                    COALESCE(new.instertedon, julianday('now')), 
                    COALESCE(new.updatedby, new.insertedby), 
                    COALESCE(new.updatedon, julianday('now')),
                    (select DBTS + 1 from EVENT_SETTINGS where rowid = 1));

        UPDATE EVENT_SETTINGS 
            SET DBTS = (select ts from CS_EVENT_PRICE_LIST where Description = new.Description),
                DBTS_TS = julianday('now')
        WHERE rowid = 1;

        SELECT RAISE(IGNORE);
        END
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = TriggerControlType = tableSQL = CREATE TABLE TriggerControl (Enabled INTEGER, TriggerType TEXT)
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = buchungType = tableSQL = CREATE TABLE buchung (ID INTEGER PRIMARY KEY,othercolumn TEXT, Datum TEXT)
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = update_buchungType = triggerSQL = CREATE TRIGGER update_buchung BEFORE UPDATE ON Buchung
    WHEN old.Datum IS NOT NULL
    BEGIN

                SELECT RAISE(FAIL, "UPDATE NOT ALLOWED");
    END
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = insert_buchungType = triggerSQL = CREATE TRIGGER insert_buchung AFTER INSERT ON Buchung 
    BEGIN 
        update Buchung SET Datum = datetime('now') WHERE ID = NEW.ID; 
    END

Пробег 4

запуск с новой установки на устройстве Android 28 (эмулятор): -

2019-06-10 12:08:23.842 12981-12981/aso.sqliteassethelpertest D/WEFTD_TOTAL: The total bytes copied was 40960
2019-06-10 12:08:23.847 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = android_metadataType = tableSQL = CREATE TABLE android_metadata (locale TEXT)
2019-06-10 12:08:23.847 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = room_master_tableType = tableSQL = CREATE TABLE room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)
2019-06-10 12:08:23.847 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = EntityTestTableType = tableSQL = CREATE TABLE `EntityTestTable` (`_id` INTEGER NOT NULL, `booleanPrimative` INTEGER NOT NULL, `charPrimative` INTEGER NOT NULL, `bytePrimative` INTEGER NOT NULL, `shortPrimative` INTEGER NOT NULL, `intPrimative` INTEGER NOT NULL, `longPrimative` INTEGER NOT NULL, `doublePrimative` REAL NOT NULL, `floatPrimative` REAL NOT NULL, `stringObj` TEXT, `booleanObj` INTEGER, `characterObj` INTEGER, `byteObj` INTEGER, `shortObj` INTEGER, `intObj` INTEGER, `longObj` INTEGER, `floatObj` REAL, `doubleObj` REAL, PRIMARY KEY(`_id`))
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = EVENT_SETTINGSType = tableSQL = CREATE TABLE EVENT_SETTINGS (
        DBTS BIGINT NOT NULL,
        DBTS_TS TIMESTAMP NOT NULL
    )
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = CS_EVENT_PRICE_LISTType = tableSQL = CREATE TABLE CS_EVENT_PRICE_LIST (
        EVENT_PRICE_LIST_ID INTEGER        PRIMARY KEY AUTOINCREMENT,
        DESCRIPTION         VARCHAR (50)   NOT NULL,
        SALES_TAX           DECIMAL (8, 2) DEFAULT 0 NOT NULL,
        TAX_TYPE            INTEGER        DEFAULT 1 NOT NULL,
        INSERTEDBY          VARCHAR (50)   NOT NULL,
        INSTERTEDON         TIMESTAMP      NOT NULL,
        UPDATEDBY           VARCHAR (50)   NOT NULL,
        UPDATEDON           TIMESTAMP      NOT NULL,
        TS                  BIGINT         NOT NULL
    )
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = sqlite_sequenceType = tableSQL = CREATE TABLE sqlite_sequence(name,seq)
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = ti_CS_EVENT_PRICE_LIST_standardType = triggerSQL = CREATE TRIGGER ti_CS_EVENT_PRICE_LIST_standard BEFORE INSERT ON CS_EVENT_PRICE_LIST
    WHEN (SELECT Enabled FROM TriggerControl WHERE TriggerType = 'productTables')
        BEGIN 
        INSERT INTO CS_EVENT_PRICE_LIST ( Description, Sales_Tax, Tax_Type, insertedby, instertedon, updatedby, updatedon, ts)
            VALUES (new.Description, new.Sales_Tax, new.Tax_Type,
                    new.insertedby, 
                    COALESCE(new.instertedon, julianday('now')), 
                    COALESCE(new.updatedby, new.insertedby), 
                    COALESCE(new.updatedon, julianday('now')),
                    (select DBTS + 1 from EVENT_SETTINGS where rowid = 1));

        UPDATE EVENT_SETTINGS 
            SET DBTS = (select ts from CS_EVENT_PRICE_LIST where Description = new.Description),
                DBTS_TS = julianday('now')
        WHERE rowid = 1;

        SELECT RAISE(IGNORE);
        END
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = TriggerControlType = tableSQL = CREATE TABLE TriggerControl (Enabled INTEGER, TriggerType TEXT)
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = buchungType = tableSQL = CREATE TABLE buchung (ID INTEGER PRIMARY KEY,othercolumn TEXT, Datum TEXT)
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = update_buchungType = triggerSQL = CREATE TRIGGER update_buchung BEFORE UPDATE ON Buchung
    WHEN old.Datum IS NOT NULL
    BEGIN

                SELECT RAISE(FAIL, "UPDATE NOT ALLOWED");
    END
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = insert_buchungType = triggerSQL = CREATE TRIGGER insert_buchung AFTER INSERT ON Buchung 
    BEGIN 
        update Buchung SET Datum = datetime('now') WHERE ID = NEW.ID; 
    END

SQLiteAssetHelper, простое решение

Приведенный выше код содержит закомментированные строки для использования предложенного SQLIteAssethelper, который работает как для pre, так и для Android Pie +, и в этом случае гораздо проще: Database Helper: -

public class DBAssetHelper extends SQLiteAssetHelper {

    public static final String DBNAME = "mytestdb";
    public static final int DBVERSION = 1;

    public DBAssetHelper(Context context) {
        super(context, DBNAME, null, null, DBVERSION);
    }
}
  • обратите внимание, что закомментированное тестирование использовало тот же файл базы данных, но с именем mytestdb и помещалось в папку assets / database (как того требует SQLiteAssetHelper).
  • дополнительно, чтобы использовать SQLiteAssethelper, вам нужно включить строку: -

    • implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:+'
  • в разделе зависимостей приложения build.gradle

  • Хотя и не поддерживается, это все же осуществимый и надежный вариант. Если вы посмотрите на код, он не использует get ???? Database, но использует методы File, чтобы проверить, существует ли база данных, а также создать каталог баз данных, если необходимо.

0 голосов
/ 10 июня 2019

Благодаря CommonsWare и SQLiteAssetHelper приведен упрощенный код для копирования файла базы данных из ресурсов в приложение.

   //in a class which extends SQLiteOpenHelper
   public void importDefault() throws Exception {

        String assetPath = "db/myApp.default.db";
        String deviceDbDirectory;
        InputStream is;

        is = contexte.getAssets().open(assetPath);
        deviceDbDirectory = getWritableDatabase().getPath();
        writeExtractedFileToDisk(is, new FileOutputStream(deviceDbDirectory));
    }


    private void writeExtractedFileToDisk(InputStream in, OutputStream outs) throws IOException {
        byte[] buffer = new byte[1024];
        int length;
        while ((length = in.read(buffer))>0){
            outs.write(buffer, 0, length);
        }
        outs.flush();
        outs.close();
        in.close();
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...