上篇文章已经介绍了如何使用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";
}
在定义数据库中的字段名时,通常会定义值为 _id 的字段作为数据表中的自增长字段,这是为了在读取数据表时使用android.widget.CursorAdapter适配器子类可以正常创建其实例化对象。否则在使用
CursorAdapter
适配器实例化对象时可能会抛出java.lang.IllegalArgumentException异常。
创建数据库
与数据库中包含数据表的结构相对应,这里定义继承自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 一致,可以分页查找。
对于
query()
方法返回的Cursor
类型,可以使用isX()
系列方法判断当前状态X,使用moveX()
系列方法将当前游标移动到某条数据对应位置,使用getX()
系列方法获取游标当前位置对应条目数据的各字段及对应值。在使用完游标结果之后,一定要使用close()
方法关闭当前游标,否则在下次查询数据时将依然返回当前的游标结果。
以上四种增删改查对应的操作方法,都可以使用原生的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语句相关内容。