SQLiteOpenHelper基礎知識

一、SQLiteOpenHelper概念

SQLiteOpenHelper是android系統提供的用於創建及操作數據庫的工具類。該類中提供了創建、升級、降級時的回調方法,對應onCreate(),onUpgrade(),onDowngrade()。在使用時可以根據app功能進行覆寫相應的方法,從而實現正確的存儲數據。

二、SQLiteOpenHelper對象的構造

由於SQLiteOpenHelper是abstract類型的,所以在使用時必需繼承此類,並實現onCreate()和onUpgrade()抽象方法。其中onDowngrade()方法已經默認實現了,其具體的邏輯只是拋出一個異常說明不能降級數據庫。
SQLiteOpenHelper的構造方法主要有以下三個(android版本不同,提供的構造方法也不同,但主要是如下方法的變種),如下:

1、public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable CursorFactory factory, int version)

2、public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable CursorFactory factory, int version, @Nullable DatabaseErrorHandler errorHandler)

3、public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,@Nullable CursorFactory factory, int version,int minimumSupportedVersion, @Nullable DatabaseErrorHandler errorHandler)

在構造方法上,有一段如下的說明註釋

Create a helper object to create, open, and/or manage a database.This method always returns very quickly. The database is not actually created or opened until one of {@link #getWritableDatabase} or {@link #getReadableDatabase} is called.

從中可知,構造SQLiteOpenHelper並不會直接生成具體的數據庫文件。
在構造方法中,都包含了name和version這兩個參數。即數據庫的文件名和數據庫的版本。在處理數據庫的升級或降級時需手動的修改version這個參數值。源代碼中,name參數的說明如下:

name of the database file, or null for an in-memory database

如果name傳入的是null,那麼系統會將數據庫存在內存中。當應用被殺死的時候,數據庫的內容也會被丟失。

1、CursorFactory

CursorFactory類是一個接口類,該類用於創建一個Cursor的子類。如果想實現自已的cursor邏輯,那麼則可以傳入自定義的CursorFactory對象。如果傳入null,那麼SQLiteOpenHelper將使用默認的cursor,即SQLiteCursor。

2、DatabaseErrorHandler

DatabaseErrorHandler定義了當數據庫發生損毀時執行的操作。類說明如下:

An interface to let apps define an action to take when database corruption is detected.

系統提供了默認的實現類,DefaultDatabaseErrorHandler。一般情況使用默認的即可。代碼如下:

mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler();

默認的操作是當數據庫發生損毀時,刪除相關的數據及日誌文件。具體見源代碼實現。

三、onCreate()方法分析

onCreate()方法不是在類加載的時候調用,而是在程序調用getWritableDatabase/getReadableDatabase的時候調用的。有篇博客說這個正好可以解釋SQLiteOpenHelper類中爲什麼帶了open這個單詞。android中的一些變量及類的命名確實很精確和嚴謹。
SQLiteOpenHelper中getWritableDatabase的方法註釋部份如下:

Create and/or open a database that will be used for reading and writing.
The first time this is called, the database will be opened and
{@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be called.

在onCreate()方法中,常常進行數據庫表的創建操作。通過參數SQLiteDatabase對象提供的execSQL()方法,執行sql語句。如下:

// 如下sql可用navicat工具創建好,在導出sql,複製粘貼到這裏即可。其中自增的字段要設置成主鍵,否則創建失敗。
var sqlStr = "CREATE TABLE user (\n" +
        "\"id\"  INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" +
        "\"name\"  TEXT NOT NULL,\n" +
        "\"age\"  INTEGER,\n" +
        "\"phone\"  TEXT\n" +
        ");"
try {
    db?.execSQL(sqlStr)     // sql 不能包含多條sql語句,只能單條。詳見方法說明。
} catch (e: Exception) {
    e.printStackTrace()
}

其中數據庫的創建語句,可以利用可視化的工具(如navicat)創建,在導出sql語句,複製粘貼即可。 onCreate()方法成功執行後,則會生成user表。
注意,sqlite中設置了autoincrement的列必需要設置成not null,否則sql語句會執行失敗。

四、onUpgrade()方法分析

應用中調用了getWritableDatabase/getReadableDatabase方法的時候,如果應用沒有舊數據庫存在,則會回調onCreate(), 如果舊數據庫的版本號小於當前設置的版本號,則會調用此方法實現數據庫的升級邏輯。系統回調的代碼部份如下

final int version = db.getVersion();
if (version != mNewVersion) {
    if (db.isReadOnly()) {
        throw new SQLiteException("Can't upgrade read-only database from version " +
                db.getVersion() + " to " + mNewVersion + ": " + mName);
    }

    if (version > 0 && version < mMinimumSupportedVersion) {
        File databaseFile = new File(db.getPath());
        onBeforeDelete(db);
        db.close();
        if (SQLiteDatabase.deleteDatabase(databaseFile)) {
            mIsInitializing = false;
            return getDatabaseLocked(writable);
        } else {
            throw new IllegalStateException("Unable to delete obsolete database "
                    + mName + " with version " + version);
        }
    } else {
        db.beginTransaction();
        try {
            if (version == 0) {
                onCreate(db);
            } else {
                if (version > mNewVersion) {
                    onDowngrade(db, version, mNewVersion);
                } else {
                    onUpgrade(db, version, mNewVersion);
                }
            }
            db.setVersion(mNewVersion);
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
    }
}

從代碼可以看出,當舊version與新version相同的時候,onCreate(), onUpgrade(), onDowngrade()方法都不會回調。 故可以推斷出,onCreate(), onUpgrade(), onDowngrade()這三個方法只會執行一次。

1、表結構的修改

數據庫升級時,往往有修改表結構的需求。如修改主鍵,增加列,改字段長度等。實現的方式有多種,可以採用創建新表,將舊錶的數據導入,也可以使用update語句修改。例如想在上面創建的user表的基礎上增加一列addr,採用創建新表的方式,代碼如下:

fun createTable2(db: SQLiteDatabase?) {
    // version 1 to 2
    // 將舊錶改名,數據都保存在這裏
    var renameTableSql = "ALTER TABLE user RENAME TO user_backup"
    db?.execSQL(renameTableSql)
    Log.d(SH_TAG, "alter table name");

    // 創建新表
    // version 2 增加一個字段addr
    var sqlStr = "CREATE TABLE user (\n" +
            "\"id\"  INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" +
            "\"name\"  TEXT NOT NULL,\n" +
            "\"age\"  INTEGER,\n" +
            "\"phone\"  TEXT,\n" +
            "\"addr\"  TEXT DEFAULT 武漢市,\n" +
            "\"gender\"  INTEGER DEFAULT 1\n" +
            ");"
    db?.execSQL(sqlStr)
    Log.d(SH_TAG, "alter table version2");

    // 將舊錶數據導入新表, 新表的增加的字段有默認值。 這裏一定要注意默認值的問題,否則列數量不對等,容易出現問題。
    var insertDataSql = "insert into user(name, age, phone) SELECT name, age, phone from user_backup"
    db?.execSQL(insertDataSql)
    Log.d(SH_TAG, "import data from old table");

    // 刪除舊錶
    var deleteOldTableSql = "DROP table user_backup"
    db?.execSQL(deleteOldTableSql)
    Log.d(SH_TAG, "delete old table");
}

採用update語句如下:

var addColumnSql = "alter table user add addr text DEFAULT '武漢'"
db?.execSQL(addColumnSql)

兩種方式,前者是重新創建並從舊錶導入數據,而後者只涉及修改,開銷比前者小。所以只在簡單的增加一列的情況下,儘量選擇後者。如果涉及複雜的修改,則建議用前者,這樣出錯的機率比較小。

2、跨版本升級時的注意事項

在處理升級數據庫時,一定要注意跨版本升級。當應用跨版本升級時,其跨過的數據庫版本的邏輯也要執行一次。如下代碼

 override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
    Log.d(SH_TAG, "oldVersion is $oldVersion and newVersion is $newVersion");
    when(newVersion) {
        2 -> {
            createTable2(db)
        }
        3 -> {
            createTable2(db)
            createTable3(db)
        }
    }

    // version 2 to 3
    // 數據庫版本升級要依次過每一個version。

    // version 3 to 4
}

當數據庫版本從1升級到3時,其也要將數據庫版本2的升級邏輯執一次。注意,當前版本的onCreate()方法也要相應的修改,因爲用戶全新安裝時是從數據庫最新版本開始的,不會執行升級過程。

五、onDowngrade()方法分析

在實際中,遇到數據庫降級的情況不多。況且android正常安裝流程(adb安裝也要加入-d參數)是不允許安裝降級應用的。該方法系統的默認實現是拋出異常,如下:

throw new SQLiteException("Can't downgrade database from version " +
                oldVersion + " to " + newVersion);

如果實現自已的降級邏輯,則可以利用sql語句刪除對應版本的邏輯。

六、數據庫的增刪改查

數據庫的增刪改查,可以通過SQLiteDatabase提供的execSQL()方法執行sql語句(該方式不能進行查詢操作,因爲execSQL是沒有返回值的)也可以通過SQLiteDatabase提供的增刪改查的方法。

1、執行原始sql語句

如下代碼對應增、刪、改。由於execSQL方法沒有返回值,故無法獲知sql語句是否執行成功。如果想要獲取執行狀態,則可以利用系統提供的標準接口。

var insertSql = "insert into user (name, age, phone, addr, gender) values(\"hahaha\", \"18\", \"13434323456\", \"武漢市\", 1)"
writeableDatabase.execSQL(insertSql)

var deleteSql = "delete from user where name like '%haha%'"
writeableDatabase.execSQL(deleteSql)

var updateSql = "update user set name = 'hello' where name like '%ha%'\n"
writeableDatabase.execSQL(updateSql)

2、通過提供的方法

查詢方法如下:

var cursor = writeableDatabase.query("user", arrayOf("name"), "name like ?", arrayOf("%張三%"), "name", null, null)
while (cursor != null && cursor.moveToNext()) {
    var column = cursor.getColumnIndex("name")
    Log.i("sh", "name is " + cursor.getString(column))
}

其中需要注意的是,where clause中不能帶%,%要放在selectionArgs中

插入的方法如下:

var contentValues = ContentValues()
contentValues.put("name", "sh")
contentValues.put("age", "27")
contentValues.put("phone", "136****2001")
var rowId = writeableDatabase.insert("user", null, contentValues)

刪除的方法如下:

var rowId = writeableDatabase.delete("user", "name = ? or name = ?", arrayOf("張三丰", "李四"))

更新的方法如下:

var contentValues4 = ContentValues()
contentValues4.put("name", "hondar")
var rowId2 = writeableDatabase.update("user", contentValues4, "name = ?", arrayOf(""))

七、總結

1、數據庫是在調用SQLiteOpenHelper的getWritableDatabase/getReadableDatabase方法時才創建的,而不是在new SQLiteOpenHelper的時候。

2、SQLiteOpenHelper的onCreate()、onUpgrade()、onDowngrade(),在一次安裝期間只會調用其中的一個方法。

3、在處理升級的時候,必需考慮到跨版本的升級。並有要考慮最新版本的新安裝的用記數據庫,即onCreate裏的數據庫初始化內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章