Android SQLite 数据库开发核心探索

Android 自带的数据库系统是 SQLite,SQLite 是 C 和 C++ 实现,因此 Android 在 Framework 层封装了一层 Java 接口。主要的封装类型为 SQLiteOpenHelper、SQLiteDatabase 以及 Cursor。不管如何封装,最后都是将 SQL 语句提交到 SQLite 中执行。

1. SQLiteOpenHelper

该类封装了 SQLite 数据库的创建与升级等工作,通过该类也可以获取到实际操作数据库的 SQLiteDatabase 对象。

通常我们会继承 SQLiteOpenHelper 类,然后实现它的 onCreate、onUpgrade 函数,这两个函数分别在创建数据库、数据库升级时调用。因此可以在 onCreate 函数中创建表,在 onUpgrade 时升级数据库。

代码大致如下:

public class SQLiteDbHelper extends SQLiteOpenHelper {

    public static final String DB_NAME = "database.db";

    public static final int DB_VERSION = 1;


    public SQLiteDbHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        // 构造函数中指定数据库名与版本号
        super(context, DB_NAME, factory, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

        // 在这里通过 db.execSQL 函数执行 SQL 语句创建所需要的表
    }

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

        // 数据库版本号变更会调用 onUpgrade 函数,在这根据版本号进行升级数据库
        switch (oldVersion) {
            case 1:
                // do something
                break;

            default:
                break;
        }
    }

    @Override
    public void onOpen(SQLiteDatabase db) {
        super.onOpen(db);
        if (!db.isReadOnly()) {
            // 启动外键
            db.execSQL("PRAGMA foreign_keys = 1;");

            //或者这样写
            String query = String.format("PRAGMA foreign_keys = %s", "ON");
            db.execSQL(query);
        }
    }
}

可以看到,每个方法都有一个 SQLiteDatabase 参数,所以真正操作数据库的类型不是 SQLiteOpenHelper,而是 SQLiteDatabase。


2. SQLiteDatabase

SQLiteDatabase 是 SQLite 数据库的管理类,提供了对 SQLite 数据库操作的接口,例如数据库的增、删、查、改。下面我们来看看它的核心接口。

2.1 插入数据

SQLiteDatabase 插入数据的接口为 insertWithOnConflict,它的声明如下:

public long insertWithOnConflict(String table, String nullColumnHack,
            ContentValues initialValues, int conflictAlgorithm) 

参数 1 表示要操作的表名,参数 2 当参数 3 为空时将表中的字段值置为 null,参数 3 为表中各字段的键值对(键的名字与表中的字段名要一致),参数 4 为当插入数据发生冲突时如何解决。

参数 4 conflictAlgorithm 的可选值有 6 种,这些策略是声明在 SQLiteDatabase 类中。

策略 说明
CONFLICT_NONE 忽略冲突
CONFLICT_ROLLBACK 停止执行当前事务,并将数据恢复到操作前的状态
CONFLICT_ABORT 停止执行当前事务,不过先前执行的操作造成的数据变动会被保留
CONFLICT_FAIL 停止执行当前事务,不过先前执行的操作造成的数据变动会被保留
CONFLICT_IGNORE 本次数据更改不会生效,但是后续的 SQL 语句会继续执行
CONFLICT_REPLACE 当插入或者修改数据时违反了唯一性原则,新的数据会替换旧的数据
2.2 删除数据
public int delete(String table, String whereClause, String[] whereArgs) 

delete 函数的参数 1 为表名,参数 2 为 where 条件,参数 3 为 where 条件语句中的字段值。其实参数 2 就是定义 column 限制条件的字符串,参数 3 则是定义参数的值。

下面我们来实现一个删除数据操作:

// 原生的 sql 语句
delete from students where name = 'Jerry' and cls_id = 2;


// 通过 delete 函数实现
db.delete("students", "name = ? and cls_id = ?", 
new String[] {"Jerry", "2"});

whereClause 中的占位符 “?” 会被替换为 whereArgs 中的值,最终拼接为原生的 SQL 语句。

2.3 修改数据
public int updateWithOnConflict(String table, ContentValues values,
            String whereClause, String[] whereArgs, int conflictAlgorithm) 

参数 1 是表名,参数 2 是要更新的数据的键值对,参数 3 为 where 语句,参数 4 是 where 语句中的参数值,参数 5 是发生冲突时的处理策略。

2.4 查询数据
public Cursor query(String table, String[] columns, String selection,
            String[] selectionArgs, String groupBy, String having,
            String orderBy, String limit) {
参数名 作用
table 要操作的表名
columns 要获取的字段数组
selection 条件语句,也就是 where 那部分
selectionArgs where 语句中的字段值
groupBy 与 SQL 中的 group by 语句一样
having Group by 中的 having 语句
orderBy SQL 语句中的排序语句
limit 限制返回的数据数量与偏移量

3. Cursor

当执行查询语句时,返回的数据类型为 Cursor。它是一个提供了随机读写访问数据库查询结果集的接口。Cursor 不是线程安全的,因此当在多线程中访问 Cursor 对象时要手动进行同步,避免出现线程安全问题。

常用的 Cursor 函数如下:

函数名 作用
getString(index) 通过字段的索引获取一个 String 类型的字段
getColumnIndex(String columnName) 通过字段名查询该字段在数据库中的索引
getCount() 在该 Cursor 中有多少条数据
MoveToFirst() 将光标移到第一个数据的位置上
MoveToLast() 将光标移到最后一个数据的位置上
MoveToNext() 将光标移到下一个数据的位置上
IsClosed() 判断光标是否已经关闭
IsFirst 光标是否在第一个数据的位置上
IsLast() 光标是否在最后一个数据的位置上
Close() 关闭光标。在使用完 Cursor 之后一定要关闭光标。
  • 通过 index 获取其他类型的数据与 getString(int index) 类似,只是函数名不一样,如 getInt(int index) 为获取整型的字段值。
  • 通过索引获取字段值效率较高,因此,通常通过字段名获取索引 - getColumnIndex(String columnName),然后再通过索引获取字段值 - getString(int index)。
如何通过 Cursor 获取所有数据?

Cursor 中定义了一个光标位置,该光标默认指向第一个数据。用户可以通过 getString、getInt、getDouble 等函数获取到各个字段的值,然后通过 moveToNext 函数不断移动光标获取下一个数据,不断重复该过程就可以获取到所有数据。

以获取 students 表的数据为例子:

    private void queryStudents() {
        SQLiteDbHelper helper = new SQLiteDbHelper(getApplicationContext());
        SQLiteDatabase database = helper.getWritableDatabase();

        // 相当于 select * from students 语句
        Cursor cursor = database.query("students", null, null, null,
                null, null, null, null);

        // 不断移动光标获取值
        while (cursor.moveToNext()) {
            // 直接通过索引获取字段值
            int stuId = cursor.getInt(0);

            // 先获取 name 的索引值,然后再通过索引获取字段值
            String stuName = cursor.getString(cursor.getColumnIndex("name"));
            Log.e("", "id: " + stuId + " name: " + stuName);

        }
        // 关闭光标
        cursor.close();
    }

4. Android 中的数据库事务

使用事务的两大好处是原子提交和更优性能。SQLite 默认会为每个插入、更新操作创建一个事务,并且在每次插入、更新后立即提交。

例如:连续插入 1000 次数据,那么实际的执行过程是重复 1000 次:创建事务 —> 执行语句 —> 提交。
如果使用数据库的事务,那么这个过程就会优化为:创建事务 —> 执行 1000 条语句 —> 提交,这样创建事务和提交这个过程只做一次,这种一次性事务可以使性能大幅提升。尤其当数据库位于 SD 卡时,时间上能节省两个数量级左右。

Android 中使用数据库事务的代码一般如下:

    private void beginTransaction() {
        SQLiteDbHelper helper = new SQLiteDbHelper(getApplicationContext());
        SQLiteDatabase database = helper.getWritableDatabase();

        database.beginTransaction();
        try {
            // 这里执行数据库操作
            // 如果执行成功则调用 setTransactionSuccessful 函数
            database.setTransactionSuccessful();
        } finally {
            database.endTransaction();
        }
    }

你可能感兴趣的:(Android SQLite 数据库开发核心探索)