SQLite 入门简介

2018-04-17  本文已影响0人  YueJZ

SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库,这意味着与其他数据库一样,您不需要在系统中配置。

就像其他数据库,SQLite 引擎不是一个独立的进程,可以按应用程序需求进行静态或动态连接。SQLite 直接访问其存储文件。

当我们有持久化保存数据的需求时,可以采用 SQLite。

本文将介绍 SQLite 的简单使用,主要从以下几个方面介绍。

创建表

本应用中,数据库只存储 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地址

上一篇下一篇

猜你喜欢

热点阅读