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里的数据库初始化内容。

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