保存数据(3)-- 在SQL数据库中保存数据
对于那些反复性的或者结构化的数据,比如联系人信息,把这些数据存储到数据库中是比较理想的做法。本课程假设你已经熟悉SQL数据库的基本知识,那么将帮助你在安卓上开始使用SQLite数据库。安卓的与数据库相关的API接口定义在android.database.sqlite包下。
定义一个框架(Schema)和协议(Contract)
SQL数据库的主要原则之一就是框架:一个关于“数据库如何组织”的声明。你在创建数据库时使用的SQL声明语句就可以揭示出这个框架(个人理解,说白了,就是数据库中有几个表,表字段是什么的东东)。你可能觉得创建一个辅助类会更有用,即协议类,它非常明确地以一种系统的和自说明的形式描述了你的框架的布局。
一个协议类就是一个包含了一系列常量定义的类,这些常量包括URI,表名和列名。这个协议类允许你在包中的其他所有类中使用这些常量。这样当你在一个地方修改列名的时候,就可以把修改同时应用到其他所有地方。
一个比较好的编写协议类的做法是把那些数据库全局常量放在类的根级。然后为每一个表创建一个内部类,其中声明了这个表的所有列名。
注意:通过实现BaseColumns接口,你的内部类可以继承一个名为_ID的主键列,这个列在其他安卓类比如游标适配器(Cursor Adaptor)中使用时是必须有的。虽然有时不是必须地(即你的表可能不会被游标适配器之类的类使用的话,同样建议保留,个人意见),但是这样做会帮助你的数据库和安卓的整个framework层能很好的兼容。
例如,这个片段定义了为一个数据库表定义了表名和列名:
public final class FeedReaderContract {
// 为了防止某个人意外创建这个协议类,使用一个空的初始化构造函数,
public FeedReaderContract() {}
/* 内部类定义了表的内容 */
public static abstract class FeedEntry implements BaseColumns {
public static final String TABLE_NAME = "entry";
public static final String COLUMN_NAME_ENTRY_ID = "entryid";
public static final String COLUMN_NAME_TITLE = "title";
public static final String COLUMN_NAME_SUBTITLE = "subtitle";
...
}
}
使用SQL Helper类创建数据库
一旦你已经定义了数据库的大致内容,你应该实现那些创建或维护数据库和表的方法。这里是一些创建和删除数据库的典型声明语句:
private static final String TEXT_TYPE = " TEXT";
private static final String COMMA_SEP = ",";
private static final String SQL_CREATE_ENTRIES =
"CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
FeedEntry._ID + " INTEGER PRIMARY KEY," +
FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
... // Any other options for the CREATE command
" )";
private static final String SQL_DELETE_ENTRIES =
"DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;
与将文件保存在内部存储上很类似,安卓也会将你的数据库保存在与你的应用相关的一个私有磁盘空间里(与内部存储目录相似,通常是/data/data/<包名>/databases/)。你的数据库是安全的,默认情况下其他应用是无法访问的。
在SQLiteOpenHelper类中有一些有用的API。当你使用这个类获得指向你的数据库的引用时,系统只会在需要的时候才会执行那些诸如创建或者升级数据库这些消耗长时间的动作,而且肯定不在应用启动过程中执行。你所要做的就是调用getWritableDatabase()或者getReadableDatabase()。
注意:由于这两个函数消耗时间长,所以请确保在后台线程中调用getWritableDatabase()或者getReadableDatabase(),比如使用AsyncTask或者IntentService。
为了使用SQLiteOpenHelper,创建一个子类,覆写方法onCreate(),onUpgrade()和onOpen()。
你也许想实现onDowngrade(),但这个不是必须地。
例如,这是一个SQLiteOpenHelper类的使用例子,使用上面定义的一些命令:
public class FeedReaderDbHelper extends SQLiteOpenHelper {
// 如果你更改了数据库的框架,你必须把数据库版本号加1.
public static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = "FeedReader.db";
public FeedReaderDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_ENTRIES);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// This database is only a cache for online data, so its upgrade policy is
// to simply to discard the data and start over
db.execSQL(SQL_DELETE_ENTRIES);
onCreate(db);
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, oldVersion, newVersion);
}
}
为了访问你的数据库,你实例化这个SQLiteOpenHelper子类:
FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());
向数据库中存信息
调用方法insert(),传递参数ContentValues,可以把数据存到数据库中:
// Gets the data repository in write mode
SQLiteDatabase db = mDbHelper.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_ENTRY_ID, id);
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_CONTENT, content);
// Insert the new row, returning the primary key value of the new row
long newRowId;
newRowId = db.insert(
FeedEntry.TABLE_NAME,
FeedEntry.COLUMN_NAME_NULLABLE,
values);
insert()方法的第一个参数是表名。第二个参数提供一个列名,这样当ContentValues中某个对应列名的条目为空的时候,数据库也允许将一个空值插入对应的列中。(如果第二个参数为”null”,那么数据库中不允许插入空值。)
从数据库中读取信息
为了从数据库中读取信息,使用query()方法,传递选择标准参数和期望查询列参数。这个方法组合了insert()和update()的一些参数,不过query()中指定了要读取的列的表单,而不是要插入的。查询的返回值是一个Cursor对象。
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// 定义了一个映射,指出你想使用查询结果中的哪些列
String[] projection = {
FeedEntry._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_UPDATED,
...
};
// 如何排序查询结果
String sortOrder =
FeedEntry.COLUMN_NAME_UPDATED + " DESC";
Cursor c = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // 返回的列
selection, // where子句中的列名
selectionArgs, // where子句中的列值
null, // 不分组
null, // 不按组筛选
sortOrder // 排序顺序
);
为了查看游标中的一行,使用Cursor中的某一个移动方法,必须在每读取一个新数据前调用。通常,开始读数据前,你会调用moveToFirst(),把读位置设为结果集中的第一个位置。对于每一行,你可以调用Cursor的某一个get方法来读取某个列的值,比如getString()或getLong()。对于每一个get方法,你必须把你要读取的列的索引位置传进去,这个索引值可以通过方法getColumnIndex()或getColumnIndexOrThrow()读取。例如:
cursor.moveToFirst();
long itemId = cursor.getLong(
cursor.getColumnIndexOrThrow(FeedEntry._ID)
);
从数据库中删除信息
为了从表中删除一行,你需要提供一个选择哪个行的参数即选择标准。数据库API集中提供一个创建这个选择标准的机制,可以防止SQL注入。这个机制把选择语句分成一个个选择子句和选择参数。子句定义了需要查看的列名。参数是那些子句中定义的列名对应的值。这样执行的时候,会采取一一对应测试,而非按照常规SQL声明执行,那么就可以防止SQL注入:
// Define 'where' part of query.
String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
// Specify arguments in placeholder order.
String[] selectionArgs = { String.valueOf(rowId) };
// Issue SQL statement.
db.delete(table_name, selection, selectionArgs);
更新数据库
当你需要更改数据库中的某个值时,使用update()方法。
更新某个表需要结合使用insert()方法的ContenValues参数加上delete()中的where参数。
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// New value for one column
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
// Which row to update, based on the ID
String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
String[] selectionArgs = { String.valueOf(rowId) };
int count = db.update(
FeedReaderDbHelper.FeedEntry.TABLE_NAME,
values,
selection,
selectionArgs);