SQLite 入门简介

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 实例。不过在实践中,建议总是遵循以下步骤:

  1. 确认目标数据库是否存在
  2. 如果不存在,首先创建数据库,初始化数据表并初始化数据
  3. 如果存在,打开并确认 CrimeDbSchema 是否为最新
  4. 如果是旧版本,就先升级到最新版本

以上工作可借助 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地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容