上篇文章已经介绍了如何使用SharedPreferences存储键值对形式的轻量级数据,对于那些相同结构的多组数据,类似于存储Java中定义的类的多个对象属性值,如果按照键值对的形式一条条读写,需要分别定义每条数据对应的key值,是相当繁琐的。而如果可以使用数据库保存就会方便很多。

正因此,Android系统提供了对SQLite数据库的支持,在应用中创建的数据库,默认也是保存在应用程序的内部存储空间中的,这样也只有当前应用程序内部可以访问其数据库中数据。

使用纯粹的SQLiteDatabase类操作数据库

在Android系统中可以使用android.database.sqlite.SQLiteDatabase数据库类,直接操作SQLite数据库。同时借助android.database.sqlite.SQLiteOpenHelper数据库帮助类,来获取数这里的据库类。

定义数据库结构类

一般要保存的类结构与数据库结构保持一致即可,以学生信息为例,下面创建的Student类即可直接作为数据库结构类,只需按照Student类中的属性名一一对应,定义数据库中的字段名。

public final class Student {
    private String name;
    private String birthday;
    private int level;
    private Student() {}

    public static final String TABLE_NAME = "student";
    public static final String COLUMN_ID = "_id";
    public static final String COLUMN_NAME = "name";
    public static final String COLUMN_BIRTHDAY = "birthday";
    public static final String COLUMN_LEVEL = "level";
}

创建数据库

与数据库中包含数据表的结构相对应,这里定义继承自SQLiteOpenHelper的子类处理数据表间关系,并在自定义子类中实现onCreate(SQLiteDatabase db)onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)方法。
通常该类的构造方法继承自其父类SQLiteOpenHelper (Context context, String name, SQLiteDatabase.CursorFactory factory, int version),参数 context 为使用该数据库的上下文环境;参数 name 为数据库文件的名字;参数 factory 为访问数据库使用的游标工厂,通常传入 null 即可;参数 version 为数据表的版本号。
该类实现的onCreate(SQLiteDatabase db)方法会在对象创建后,数据库文件首次创建时调用,因此可以在该方法中执行数据表的创建操作,参数 db 即当前数据库对象,可调用该对象的相关方法操作数据库。
onUpdate(SQLiteDatabase db, int oldVersion, int newVersion)方法会在该类对象创建后,数据库文件存在但数据库版本升级时调用,因此在该方法中可以执行数据表的更新操作。其中参数 db 为当前数据库对象,同样调用该对象的相关方法可操作数据库;参数 oldVersion 为数据库版本升级之前的旧版本号;参数 newVersion 为数据库版本升级之后的新版本号。

以上文创建Student学生类对应的数据库为例,示例代码如下。当首次创建StudentDbHelper对象时,其对应数据库版本号为10,且数据库文件需要首次创建,因此会执行该对象的onCreate()方法,在数据库中执行SQL_CREATE_STUDENT定义的sql语句,创建不包含 birthday 字段的数据表。而如果在以后需要更新数据表时,想增加 birthday 字段,只需要在创建StudentDbHelper对象时,将其对应数据库版本号改为20,并在onUpdate()方法中做版本号的判断,一旦判断符合条件,即可执行SQL_ADD_BIRTHDAY定义的sql语句。

public class StudentDbHelper extends SQLiteOpenHelper {
    public static final int DATABASE_VERSION_FIRST = 10;
    public static final int DATABASE_VERSION_SECONDDATABASE_VERSION_SECOND = 20;

    public static final String DATABASE_NAME = "students.db";

    private static final String SQL_CREATE_STUDENT =
        "CREATE TABLE " + Student.TABLE_NAME + " (" +
        Student.COLUMN_ID + " INTEGER PRIMARY KEY," +
        Student.COLUMN_NAME + " TEXT," +
        Student.COLUMN_LEVEL + " TEXT)";

    private static final String SQL_ADD_BIRTHDAY =
        "ALTER TABLE " + Student.TABLE_NAME +
        " ADD COLUMN " + Student.COLUMN_BIRTHDAY + " TEXT";

    public StudentDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION_FIRST);
        //super(context, DATABASE_NAME, null, DATABASE_VERSION_SECOND);
    }

    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_STUDENT);
    }

    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if(newVersion==DATABASE_VERSION_SECOND){
            db.execSQL(SQL_DELETE_BIRTHDAY);
        }
    }

}

操作数据库

上边在SQLiteDbHelper子类中已经对SQLiteDatabase类有所了解,除此之外,也可以在需要获取数据库对象的地方通过SQLiteDbHelper对象的getWritableDatabase()方法获取可读写数据库和getReadableDatabase()方法获取可读式数据库。

在获取到SQLiteDatabase数据库对象之后,可通过其相关方法分别执行数据库的增删改查等操作。

调用该对象的insert(String table, String nullColumnHack, ContentValues values)系列方法,可以在数据库中插入一条数据。返回 long 类型的结果表示插入数据在数据表中的id序列,如果插入失败则返回 -1 。其中参数 table 为要插入的数据表名;参数 nullColumnHack 可以指定要插入的字段名,通常为null时忽略,使用后边参数中所包含的字段数据;参数 values 指定要插入的数据,同样使用 key-value 键值对的形式存取数据。

调用该对象的delete(String table, String whereClause, String[] whereArgs)方法,可以删除数据库中指定的数据。返回 int 类型的结果表示删除的数据条数。其中参数 table 同样为要删除的数据表名;参数 whereClause 为指定删除条件,其符合sql语句,但变量参数可用?代替,在后边参数中指定具体参数值;参数 whereArgs 即为参数值数组,长度与参数 whereClause 中的?符合数量一致。如果删除某个数据表中的所有内容,只需将参数二和参数三均置为null即可。

调用该对象的update(String table, ContentValues values, String whereClause, String[] whereArgs)方法,可以更新数据库中指定的数据。返回 int 类型的结果表示更新的数据条数。其中参数 table 同样为要更新的数据表名;参数 values 指定要更新的字段数据;参数 whereClause 可以指定更新条件;参数 whereArgs 对应指定更新条件中的参数值。这里如果参数三和参数四均为null,则会更新数据表中所有数据条目。

调用该对象的query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)系列方法,可以查询数据库中的指定数据。返回android.database.Cursor类型的游标对象,可以暂存多条结果。其中参数 table 为要查询的数据表名;参数 columns 为返回的结果中所包含的字段,如果为null则返回所有字段;参数 selection 为查询条件,同样符合sql语句,但变量参数用?代替;参数 selectionArgs 对应于查询条件中变量参数的值所组成的数组;参数 groupBy 与sql语句中的 GROUP BY 一致,可以指定返回的数据中以某个字段为分组依据,如果为null则不会分组;参数 having 同样与sql语句中的 HAVING 一致,指定返回的数据中是否包含某个字段,如果为null则包含所有数据;参数 orderBy 同样与sql语句中的 ORDER BY 一致,可以指定根据某个字段排序,如果为null则不会排序;参数 limit 与sql语句中的 LIMIT 一致,可以分页查找。

以上四种增删改查对应的操作方法,都可以使用原生的sql语句实现,所以可以直接调用SQLiteDatabase对象的execSQL (String sql)方法,传入一条已经定义好的sql语句即可。该方法在上文创建数据库时已有使用示例,可支持大多数sql语句。

在数据库中有事务的概念,也就是将多个增删改查操作线性执行看成一个整体的操作。同样在Android中,通过调用SQLiteDatabase对象的begainTransaction()方法可以启动一次事务,在所有事务操作执行结束后,再调用setTransactionSuccessful()方法标志当前事务操作以完成,如果不调用该方法,当前事务即便被提交也不会执行。最终再调用endTransaction()方法以结束当前事务,并判断在调用上述setTransactionsuccessful()标志方法条件下提交并执行当前事务。

释放数据库资源

在对数据库的所有操作结束之后,一般是在使用SQLiteDbHelper对象所在的组件生命周期结束之前,要调用SQLiteDbHelper对象的close()方法,以释放应用程序对数据库的资源占有。

借助更便捷的Room框架

为了在开发中更聚焦于业务代码逻辑,而简化数据库的开关流程,像Room这种开发级框架也就应运而生了。Room框架是有官方提供的推荐框架,除此之外,还有其他开发者或组织提供的优秀框架,包括但不限于GreenDao、LitePal、OrmLite等。由于开发级框架使用便捷,虽性能各有优劣,但使用大同小异,这里不再赘述。


简单开发中对数据库的使用只集中在增删改查的简单操作中,尤其是查询数据往往取出结果后会做进一步的过滤处理,如果能在查询操作时巧妙的借助 GROUP BY 这种子语句,其效率定能更快一步,这里多是开发人员容易忽略的一点。总之合理使用数据库,对大量相同结构数据的存储是很高效的,同时如果想进一步提高数据库性能,建议多学习下sql语句相关内容。

09-11 06:40