SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库,这意味着与其他数据库一样,您不需要在系统中配置。
就像其他数据库,SQLite 引擎不是一个独立的进程,可以按应用程序需求进行静态或动态连接。SQLite 直接访问其存储文件。
当我们有持久化保存数据的需求时,可以采用 SQLite。
本文将介绍 SQLite 的简单使用,主要从以下几个方面介绍。
- 创建表
- insert 和 updata 操作
- 查询操作
- 在开发时查看数据库结构及数据
创建表
本应用中,数据库只存储 Crime 类的数据,较为简单,我们直接定义 schema 的 java 类。新建 database 包 database.CrimeDbSchema。创建 CrimeDbSchema 类。
CrimeTable.java
public class CrimeDbSchema {
public static final class CrimeTable{
public static final String NAME = "crimes";
public static final class Cols{
public static final String UUID = "uuid";
public static final String TITLE = "title";
public static final String DATE = "date";
public static final String SOLVED = "solved";
}
}
}
CrimeTable 类唯一的作用就是描述数据库表名,Cols 类描述数据库的列名。有了这些数据表元素,就可以在 Java 代码中安全引用了。此外,这还为修改字段名或者新增表元素都带来了方便。
有了数据库 schema,就可以创建数据库了。openOrCreateDatabase(...) 和 databaseList() 是Android 提供的 Context 底层方法,用来打开数据库文件并转化为 SQLiteDatabase 实例。不过在实践中,建议总是遵循以下步骤:
- 确认目标数据库是否存在
- 如果不存在,首先创建数据库,初始化数据表并初始化数据
- 如果存在,打开并确认 CrimeDbSchema 是否为最新
- 如果是旧版本,就先升级到最新版本
以上工作可借助 Android 的 SQLiteOpenHelper 类处理。同样在数据库包中创建该类。
BaseHelper.java
public class CrimeBaseHelper extends SQLiteOpenHelper {
private static final int VERSION = 1;
private static final String DATABASE_NAME = "crimeBase.db";
public CrimeBaseHelper(Context context){
super(context,DATABASE_NAME,null,VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
onCreate(...) 负责创建数据库,onUpgrade(...) 负责与升级相关的工作。接下来在 CrimeLab 中使用 CrimeBaseHelper 类。
CrimeLab.java
public class CrimeLab {
private static CrimeLab sCrimeLab;
private List<Crime> mCrimes;
private Context mContext;
private SQLiteDatabase mDatebase;
private CrimeLab(Context context) {
mContext = context.getApplicationContext();
mDatebase = new CrimeBaseHelper(mContext)
.getWritableDatabase();
mCrimes = new ArrayList<>();
}
}
调用 getWritableDatabase() 时,CrimeHelper 会做上面提到的四步工作。这里我们还需要把 onCreate(...) 中的 SQL 补充完整。
CrimeBaseHelper.java
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL("create table " + CrimeDbSchema.CrimeTable.NAME + "(" + "_id integer primary key autoincrement,"
+ CrimeDbSchema.CrimeTable.Cols.UUID + "," + CrimeDbSchema.CrimeTable.Cols.TITLE + "," + CrimeDbSchema.CrimeTable.Cols
.DATE + ","+ CrimeDbSchema.CrimeTable.Cols.SOLVED + ")");
}
现在运行程序,只会看到空列表,因为我们只创建了数据库,里面并没有值,下面我们逐步来完善它。
insert 和 updata 操作
修改 CrimeLab 类。负责数据库写入和更新操作的辅助类是 ContentValues。它类似与 HashMap 的键值存储类。但它只能处理 SQLite 数据。
CrimeLab.java
private static ContentValues getContentValues(Crime crime){
ContentValues values = new ContentValues();
values.put(CrimeDbSchema.CrimeTable.Cols.UUID,crime.getId().toString());
values.put(CrimeDbSchema.CrimeTable.Cols.DATE,crime.getDate().getTime());
values.put(CrimeDbSchema.CrimeTable.Cols.SOLVED,crime.isSolved() ? 1 : 0);
values.put(CrimeDbSchema.CrimeTable.Cols.TITLE,crime.getTitle());
return values;
}
准备好了 ContentValues 类,就该向数据库写入数据了。
CrimeLab.java
public void addCrime(Crime c){
ContentValues values = getContentValues(c);
mDatebase.insert(CrimeDbSchema.CrimeTable.NAME,null,values);
}
insert(String,String,ContentValues) 的第一个参数是数据库表名,第三个参数是要写入的数据,第二个参数很少用到,当传入的 ContentValues 参数为空时,insert(...) 方法会调用失败,但如果以 uuid 做为第二个参数的值,那么就可以插入该值。我们现在用不到。
现在来写更新的方法
CrimeLab.java
public void updateCrime(Crime crime){
String uuidString = crime.getId().toString();
ContentValues values = getContentValues(crime);
mDatebase.update(CrimeDbSchema.CrimeTable.NAME,values, CrimeDbSchema.CrimeTable.Cols.UUID +
" = ?",new String[]{uuidString});
}
update(String,ContentValues,String,String []) 方法类似于 insert(...) 方法。传入表名和 ContentValues,然后创建 where 字句(第三个参数),指定 where 字句中的参数(String [] 数组参数)。为什么不直接放在 where 语句中呢?因为这样可以防止 SQL 注入。
修改数据之后需要刷新数据,可以覆盖 CrimeFrament.onPause() 方法。
CrimeFragment.java
@Override
public void onPause() {
super.onPause();
CrimeLab.get(getActivity()).updateCrime(mCrime);
}
数据库的写入部分处理完了,如果都没有问题,那么运行程序会出现空列表。
查询操作
读取数据库中的数据需要调用 SQLiteDatabase.query(...),在 CrimeLab 中新增一个 quary 方法。
CrimeLab.java
private Cursor queryCrimes(String whereClause, String[] whereArgs){
Cursor cursor = mDatebase.query(CrimeDbSchema.CrimeTable.NAME,
null,
whereClause,
whereArgs,
null,
null,
null,
null);
return cursor;
}
whereClause 和 whereArgs 与 update(...) 方法中参数的作用类似。
Cursor 类的功能是封装数据库中的原始字段值。因为每次从数据库中取出一条数据,都会用到相同的代码,如:getString(getColumnIndex(CrimeDbSchema.CrimeTable.Cols.UUID))。考虑到代码的复用。我们创建可复用的专用 Cursor 子类。创建 Cursor 子类最简单的方式是使用 CursorWrapper。可以用 CursorWrapper 封装 Cursor 对象,然后再添加有用的扩展方法。
在数据包中新建 CrimeCursorWrapper 类。
CrimeCursorWrapper.java
public class CrimeCursorWrapper extends CursorWrapper{
public CrimeCursorWrapper(Cursor cursor){
super(cursor);
}
public Crime getCrime(){
String uuidString = getString(getColumnIndex(CrimeDbSchema.CrimeTable.Cols.UUID));
String title = getString(getColumnIndex(CrimeDbSchema.CrimeTable.Cols.TITLE));
long date = getLong(getColumnIndex(CrimeDbSchema.CrimeTable.Cols.DATE));
int isSloved = getInt(getColumnIndex(CrimeDbSchema.CrimeTable.Cols.SOLVED));
Crime crime = new Crime(UUID.fromString(uuidString));
crime.setDate(new Date(date));
crime.setTitle(title);
crime.setSolved(isSloved != 0);
return crime;
}
}
在 Crime 类中新增一个传入 uuid 的构造方法。
Crime.java
public class Crime {
public Crime() {
this(UUID.randomUUID());
}
public Crime(UUID id){
mId = id;
mDate = new Date();
}
}
更改 queryCrimes(...) 方法的返回值
CrimeLab.java
private CrimeCursorWrapper queryCrimes(String whereClause, String[] whereArgs){
Cursor cursor = mDatebase.query(CrimeDbSchema.CrimeTable.NAME,
null,
whereClause,
whereArgs,
null,
null,
null,
null);
return new CrimeCursorWrapper(cursor);
}
完善 getCrimes() 方法:遍历取出所有的 crime,返回 Crime 数组对象。
CrimeLab.java
public Crime getCrime(UUID id) {
public List<Crime> getCrimes() {
//return new ArrayList<>();
List<Crime> crimes = new ArrayList<>();
CrimeCursorWrapper cursor = queryCrimes(null,null);
try {
cursor.moveToFirst();
while (!cursor.isAfterLast()){
crimes.add(cursor.getCrime());
cursor.moveToNext();
}
}finally {
cursor.close();
}
return crimes;
}
要从 cursor 中取出数据,首先要调用 movetoFirst() 方法移动到第一个元素,读取到记录后,调用 movetoNext() 方法移动到下一个数据,直到 isAfterLast() 说没有数据了为止。 最后还需要 close() 方法关闭它,否则会出错。
增加根据 uuid 查询单个 Crime 的方法。
CrimeLab.java
public Crime getCrime(UUID id) {
CrimeCursorWrapper cursorWrapper = queryCrimes(CrimeDbSchema.CrimeTable.Cols.UUID + " = ?",
new String[]{id.toString()});
try{
if (cursorWrapper.getCount() == 0){
return null;
}
cursorWrapper.moveToFirst();
return cursorWrapper.getCrime();
}finally {
cursorWrapper.close();
}
}
现在可以看到新增的 Crime 了。
还没有完,虽然 Crime 记录存入了数据库,但是数据库读取还没有完善。例如,当编辑完新的 crime,点击后退键,会发现 CrimeListActivity 并没有刷新记录。这是因为 CrimeLab 的工作方式已经变了。以前,只有一个 List<Crime>,而且每个 Crime 在 List<Crime> 中只存在一个对象。要获取哪一个只能去找 mCrimes。现在 mCrimes 已经废弃不用,所以,getCrimes() 方法返回的 List<Crime> 是 Crime 对象的快照,要刷新 CrimeListActivity 界面,首先要刷新这个快照。
要刷新 crime 显示,首先添加一个 setCrimes(...) 方法给 CrimeAdapter。
CrimeListFragment.java
private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder> {
public void setmCrimes(List<Crime> crimes){
mCrimes = crimes;
}
}
然后在 updateUI() 方法中调用 setCrimes(...) 方法。
CrimeListFragment.java
private void updateUI() {
CrimeLab crimeLab = CrimeLab.get(getActivity());
List<Crime> crimes = crimeLab.getCrimes();
if (mAdapter==null){
mAdapter = new CrimeAdapter(crimes);
mCrimeRecyclerView.setAdapter(mAdapter);
}else {
mAdapter.setmCrimes(crimes);
mAdapter.notifyItemChanged(mPosition);
}
updateSubtitle();
}
现在就能正常使用该应用了。
在开发时查看数据库结构及数据
这篇文章介绍了一种查看数据库结构及数据的工具,可点击查看
附:GitHut地址