第十四章 SQLite数据库
有关新建的三个数据库类
public class CrimeDbSchema public class CrimeBaseHelper extends SQLiteOpenHelper public class CrimeCursorWrapper extends CursorWrapper
public class CrimeDbSchema是用于定义数据库的scheme,包括表名、属性项名。
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";
}
}
}
public class CrimeBaseHelper extends SQLiteOpenHelper对应于数据库操作中的DDL(数据定义语言),这里只用到了创建一个表,里面还定义了文件名称和版本。
public class CrimeBaseHelper extends SQLiteOpenHelper {
private static final int VERSION=1;
private static String DATABASE_NAME="crimeBase.db";
public CrimeBaseHelper(@Nullable Context context) {
super(context,DATABASE_NAME , null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table "+ CrimeTable.NAME+"("+"_id integer primary key autoincrement, "+CrimeTable.Cols.UUID+", "+CrimeTable.Cols.TITLE+", "+CrimeTable.Cols.DATE+", "+CrimeTable.Cols.SOLVED+")");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
public class CrimeCursorWrapper extends CursorWrapper是一个游标封装类,public Crime getCrime()将从数据库中读到的一条记录封装变成一个crime。
public class CrimeCursorWrapper extends CursorWrapper { public CrimeCursorWrapper(Cursor cursor) { super(cursor); } public Crime getCrime() { String uuidString=getString(getColumnIndex(CrimeTable.Cols.UUID)); String title=getString(getColumnIndex(CrimeTable.Cols.TITLE)); long date=getLong(getColumnIndex(CrimeTable.Cols.DATE)); int isSolved=getInt(getColumnIndex(CrimeTable.Cols.SOLVED)); Crime crime=new Crime((UUID.fromString(uuidString))); crime.setTitle(title); crime.setDate(new Date(date)); crime.setSolved(isSolved!=0); return crime; } }
有关连接到数据库
之前的所有数据都是通过CrimeLab来进行增删改查的,现在也不例外,所以需要在CrimeLab中打开一个数据库(利用CrimeBaseHelper)。
private Context mContext; private SQLiteDatabase mDatabase; ... private CrimeLab(Context context) { mContext=context.getApplicationContext(); mDatabase=new CrimeBaseHelper(mContext).getWritableDatabase(); }
有关利用数据库进行增删改查(DML)
负责数据库写入(增)和更新(改)操作需要用到辅助类ContentValues,它和HashMap、Bundle是一个键值存储类。只不过ContentValues只能用于处理SQLite的数据。创建ContentValues就是将传入的crime打包成键值对的形式。
private static ContentValues getContentValues(Crime crime)
{
ContentValues values=new ContentValues();
values.put(CrimeDbSchema.CrimeTable.Cols.UUID,crime.getId().toString());
values.put(CrimeDbSchema.CrimeTable.Cols.TITLE,crime.getTitle());
values.put(CrimeDbSchema.CrimeTable.Cols.DATE,crime.getDate().getTime());
values.put(CrimeDbSchema.CrimeTable.Cols.SOLVED,crime.isSolved()?1:0);
return values;
}
增
改写CrimeLab.addCrime()方法:
public void addCrime(Crime c) { ContentValues values=getContentValues(c); mDatabase.insert(CrimeDbSchema.CrimeTable.NAME,null,values); }
insert第一个参数是表名,第二个是空值时的操作(没用到),第三个是插入的元组。
改
public void updateCrime(Crime crime)
{
String uuidString =crime.getId().toString();
ContentValues values=getContentValues(crime);
mDatabase.update(CrimeDbSchema.CrimeTable.NAME,values, CrimeDbSchema.CrimeTable.Cols.UUID+"= ?",new String[]{uuidString});
}
update第一个参数是表名,第二个参数是修改的结果,第三个是where语句;第三个where语句中的“?”在第四个参数中补充。
删
见挑战练习。
查
读取数据库对应select,SQLite用的是query。查询结果储存在游标cursor里面,但是我们之前为了方便已经定义了游标的封装类CrimeCursorWrapper了,所以就使用它。
private CrimeCursorWrapper queryCrimes (String whereClause, String[] whereArgs) { Cursor cursor=mDatabase.query( CrimeDbSchema.CrimeTable.NAME, null, whereClause, whereArgs, null, null, null ); return new CrimeCursorWrapper(cursor); }
上面这一段方法实际上就是将select的范围锁定在表名为CrimeDbSchema.CrimeTable.NAME的表里而已。
得到游标之后需要逐条读取每一条记录,这样才能获得所有结果。读取每一条记录并将其封装为Crime类的方法就是之前创建的CrimeCursorWrapper的getCrime()方法。接下来在说明如何用游标来读取数据。
读-获取所有记录
读取所有记录并存在一个list中以便于像前几章一样供Fragment读取。所以需要重写CrimeLab.getCrimes()方法。
public List<Crime>getCrimes()
{
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;
}
读-获取某一个特定的记录
原先的CrimeLab.getCrime()方法是获取指定Id的记录。在前几张的挑战练习中要求改进查询方式,当时我就说效率提升不是很大,而且之后还需要不断修改,没想到用上数据库之后可以直接全删了。
public Crime getCrime(UUID id) { CrimeCursorWrapper cursor=queryCrimes(CrimeDbSchema.CrimeTable.Cols.UUID+" =?",new String[]{id.toString()}); try { if (cursor.getCount()==0) { return null; } cursor.moveToFirst(); return cursor.getCrime(); } finally { cursor.close(); } }
有关刷新模型层数据
到此为止,新建一个crime之后RecyclerView不会立刻显示出新建的项目,甚至是你重新打开这个软件之前都不会显示。
造成这个问题的原因是RecyclerView的刷新所依据的Adapter.mCrimes是对创建其本身时对数据库的一次快照,保留的只是当时的数据,而我们对数据的增删改查全是直接对数据库进行操作,绕过了mCrimes。那么只需要每一次更新界面时,让mcrimes再重新获取一次数据库的快照就行了。
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.setCrimes(crimes);
mAdapter.notifyItemChanged(CrimePosition);
}
updateSubtitle();
}
有关挑战练习 删除Crime记录
这个简单,直接在CrimeLab方法中增加有关deleteCrime的方法。
public void deleteCrime(Crime crime) { String uuidString =crime.getId().toString(); mDatabase.delete(CrimeDbSchema.CrimeTable.NAME, CrimeDbSchema.CrimeTable.Cols.UUID+"= ?",new String[]{uuidString}); }
delete的第一个参数是要删除元组的表名,第二个参数是where语句,其中“?”由第三个参数补充。