一 前言

经常听到已经工作的程序员说每天的工作很无聊,总是一些CURD操作,没什么技术含量,对,今天我们就要勇敢的探索一下这所谓的“无聊工作”--CURD,并想办法让它有趣起来。所谓的CURD,其实就是数据库的基本操作,C代表create,U代表update,R代表read,D则代表delete。写过后台脚本的朋友对这个应该很熟悉,你就可以随便翻翻啦,而不太了解数据库操作的朋友,我在这里抛砖引玉,讲一下怎么将内存中的数据保存到本地数据库中,您看完后可以更加深入的去寻找一些资料学习,千万要记得不管别人说什么无聊或者没有技术含量,我们还是要一步一个脚印,坚持敲下去!

二 创建伴随类

前面说了我们要把这简单的操作讲的有趣些,那我就玩点套路,不直接讲API,先来看一个类:

package com.aristark.note.database;

public class NoteDbScheme {
    public static final class NoteTable{
        public static final String name = "notes";
        
        public static final class Cols{
            public static final String UUID = "uuid";
            public static final String TITLE = "title";
            public static final String CONTENT = "content";
            public static final String DATE = "date";
            public static final String TAG = "tag";
            
        }
    }
}

类的名字叫NoteDbScheme,表明它是一个关于“计划”的类,而且和database少不了干系,再来回忆Note这个类:

public class Note {
    private UUID uuid;
    private String title;
    private String content;
    private Date date;
    private String tag;

    public Note(){
        uuid = UUID.randomUUID();
        date = new Date();
    }

    public UUID getUuid() {
        return uuid;
    }

    public Date getDate() {
        return date;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setTag(String tag) {
        this.tag = tag;
    }

    public String getTag() {
        return tag;
    }
}

很容易看出来Note类里的私有字段和NoteDbScheme里常量的值是一致的,言下之意,我们可以从数据库里取值赋给Note类中的字段,也可以把Note类中字段的值存入数据库。再回忆一下之前我们是怎么用Note类的,我们在NoteLab创建了一个泛型为Note的ArrayList,然后通过NoteListActivity把它渲染到layout,也就是用户的界面上,这样看来,Note类隐隐约约好像作为一个沟通的桥梁(其实了解MVC模式的朋友应该很好理解)。

三 创建数据库

1.sql简介:了解sql知识点这里
了解sqlite知识点这里

懒得看的就直接看这段sql语句,强行接受我的解释就行了:

create table note(
    _int integer primary key autoincrement,
    uuid text,
    title text,
    content text,
    date text,
    tag text
)

这段SQL语句的意思是,创建一个名叫“note”的表,包含6个字段,以第一个字段为例,_int为字段名,integer为字段类型,primary key autoincrement表示它是主键并自增。请大家记住这个套路。

2.SQLiteOpenHelper---数据库好帮手
上面已经给出了创建表并定义字段的方法,在别的地方它可以在终端或者脚本中运行,在Android世界里自然要在类中大展宏图。如果你的英语还可以请你打开Android->sdk>doc,在官方文档中搜索SQLiteOpenHelper,你会发现你要做的这个类基本上已经帮你封装好了,你要做的就是继承它,并依照你的需求去敲敲打打就行。好,来写代码:

public class NoteBaseHelper extends SQLiteOpenHelper {
    @Override
    public void onCreate(SQLiteDatabase db) {
        
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

这里需要覆写的onCreate方法就是我们创建表的地方,当NoteBaseHelper被实例化时,它会自动检测数据库中有没有note这个表,如果没有就调用该方法。所以我们把刚刚的sql语句写进去:

@Override
public void onCreate(SQLiteDatabase db) {
    db.execSQL("create table " +NoteDbScheme.NoteTable.name+"(" +
            "_id integer primary key autoincrement,"
            +NoteDbScheme.NoteTable.Cols.UUID+","
            +NoteDbScheme.NoteTable.Cols.TITLE+","
            +NoteDbScheme.NoteTable.Cols.CONTENT+","
            +NoteDbScheme.NoteTable.Cols.DATE+","
            +NoteDbScheme.NoteTable.Cols.TAG
            +")"
    );

}

这里写的和上面其实是完全一样的,只不过我们是用NoteDbScheme中的常量代替了字符串而已,这样做的好处是如果以后表中的字段需要变更的话我们只要在NoteDbScheme中变更就行了,而不需要动这里的代码。另一个需要覆写的onUpgrade在数据库需要更新的时候调用。我们再添加两个常量来标记数据库的名字和版本:

public class NoteBaseHelper extends SQLiteOpenHelper {
    public static final int VERSON = 1;
    public static final String DATABASE_NAME = "NoteBase";
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table " +NoteDbScheme.NoteTable.name+"(" +
                "_id integer primary key autoincrement,"
                +NoteDbScheme.NoteTable.Cols.UUID+","
                +NoteDbScheme.NoteTable.Cols.TITLE+","
                +NoteDbScheme.NoteTable.Cols.CONTENT+","
                +NoteDbScheme.NoteTable.Cols.DATE+","
                +NoteDbScheme.NoteTable.Cols.TAG
                +")"
        );

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

最后别忘了创建NoteBaseHelper的构造方法:

public NoteBaseHelper(Context context) {
    super(context,DATABASE_NAME,null,VERSON);
}

DATABASE_NAME和VERSON就是我们刚才定义过的。

3.创建数据库和表
前面我们分析了,我们用NoteLab类将数据渲染到前台用户界面,同样的我们也用它来与后台数据库交互,先来回顾一下NoteLab类:

public class NoteLab {
    private static NoteLab sNoteLab; //for the global use
    private ArrayList<Note> notes;

    private NoteLab(Context context){
        notes = new ArrayList<Note>();

        //generate 100 Note Objects
//        for (int i=0;i<100;i++){
//            Note note = new Note();
//            note.setTitle("this is title "+i);
//            note.setContent("this is content "+i+"balabalabalabalalabalabalabalabalala\nbalabalabalabalalabalabalabalabalala\nbalabalabalabalalabalabalabalabalala\nbalabalabalabalalabalabalabalabalala\nbalabalabalabalalabalabalabalabalala\n");
//            notes.add(note);
//        }
    }

    public static NoteLab getNoteLab(Context context){
        if (sNoteLab == null){
            sNoteLab = new NoteLab(context);
        }

        return sNoteLab;
    }

    public ArrayList<Note> getNotes() {
        return notes;
    }

    public void addNote(Note note){
        notes.add(note);
    }

    public Note getNote(UUID uuid){
        for (Note note : notes){
            if (note.getUuid().equals(uuid)){
                return note;
            }
        }

        return null;
    }
}

每个方法的用途都可以通过命名来体现,实在看不懂的翻看我前面两篇文章就行了。
public SQLiteDatabase getWritableDatabase ()

Added in API level 1
Create and/or open a database that will be used for reading and writing. The first time this is called, the database will be opened and onCreate(SQLiteDatabase), onUpgrade(SQLiteDatabase, int, int) and/or onOpen(SQLiteDatabase) will be called.

Once opened successfully, the database is cached, so you can call this method every time you need to write to the database. (Make sure to call close() when you no longer need the database.) Errors such as bad permissions or a full disk may cause this method to fail, but future attempts may succeed if the problem is fixed.

Database upgrade may take a long time, you should not call this method from the application main thread, including from ContentProvider.onCreate().

Returns
a read/write database object valid until close() is called
Throws
SQLiteException if the database cannot be opened for writing

这是Google官方文档中关于SQLiteOpenHelper类的一个方法,用来创建或者打开一个数据库,好,我们就在NoteLab类的构造方法中来使用这个方法:

public class NoteLab {
    private static NoteLab sNoteLab; //for the global use
    private ArrayList<Note> notes;
    private Context context;
    private SQLiteDatabase database;

    private NoteLab(Context context){
        notes = new ArrayList<Note>();
        database =  new NoteBaseHelper(context).getWritableDatabase();


        //generate 100 Note Objects
//        for (int i=0;i<100;i++){
//            Note note = new Note();
//            note.setTitle("this is title "+i);
//            note.setContent("this is content "+i+"balabalabalabalalabalabalabalabalala\nbalabalabalabalalabalabalabalabalala\nbalabalabalabalalabalabalabalabalala\nbalabalabalabalalabalabalabalabalala\nbalabalabalabalalabalabalabalabalala\n");
//            notes.add(note);
//        }
    }
    ......
}

这样我们在创建NoteLab的同时也就创建了NoteBase数据库和note表。好,编译,运行,这时候打开Android Device Monitor(Android Studio问号左边的小机器人按钮)
图片描述
在Android中,所有App的本地数据库都在data->data下,找到我们这个应用的包名之下的databases目录下,我们看到了NoteBase数据库,说明我们成功创建了note表!
图片描述

四 插入数据

插入数据无非就是将一组键值对提交给操作数据的API,而这里的键值对就是以ContentValues来组装的(大家在这里一定要去官方文档搜到这个类好好读一下,不然可能会在这里卡住),我们现在在NoteLab类中添加如下方法:

   private ContentValues getValues(Note note){
        ContentValues values = new ContentValues();
        values.put(NoteDbScheme.NoteTable.Cols.UUID,note.getUuid().toString()![图片描述][5]);
        values.put(NoteDbScheme.NoteTable.Cols.TITLE,note.getTitle());
        values.put(NoteDbScheme.NoteTable.Cols.CONTENT,note.getContent());
        values.put(NoteDbScheme.NoteTable.Cols.DATE,note.getDate().toString());
        values.put(NoteDbScheme.NoteTable.Cols.TAG,note.getTag());
        return values;
    }

代码很容易读,put方法接受的就是key--value对,这里再一次用到了NoteDbScheme类,是不是觉得很奇妙很方便?现在我们改写之前的addNote方法:

    public void addNote(Note note){
//        notes.add(note);
        ContentValues values = getValues(note);
        database.insert(NoteDbScheme.NoteTable.name,null,values);
    }

这里着重掌握insert方法,第一个参数接受将要插入的表名,第三个参数就是我们需要组装的ContentValues,第二个参数是关于插入空记录的相关设置,大家可以去查看文档。好,编译,运行:图片描述
点击右上角加号,输入数据几组数据
图片描述
很遗憾我现在并不会检验数据是否写进表,不管了继续写代码,去把数据库的记录读出来!

五 读取数据

写了这么多有点写不下去了,不知道你是不是也看不下去了,你心里肯定想这太冗杂了(谁让咱们使用java写android呢,随便写写就感觉好多的样子,但是换个角度想我们可以对外吹我们的代码量很多啊哈哈,虽然并没有什么卵用)。扯店犊子,大家回顾一下C语言中的指针和java语言中的引用(句柄?handle?指针?),或者是数组中的索引,我们会发现这些重要的抽象对于我们操作数据是很有用的,对了,在Android我们有Cursor(中文意思是游标,对,就是游标卡尺的那个游标),我们可以变相的把它理解为一个带有指针的数组,我们可以通过操作“指针”来获取其中的数组。每次我们通过一定的条件进行数据库查询时,返回的就是一个Cursor, 它可以依次指向查询结果记录的每一条。不小心又说多了,来,写代码,在NoteLab中创建如下方法:

private Cursor queryNote(String whereClause,String[] whereArgs){
    Cursor cursor = database.query(
            NoteDbScheme.NoteTable.name,
            null,
            whereClause,
            whereArgs,
            null,
            null,
            null
    );
    return cursor
}

我们需要关注的是query方法,第一个参数是要查询的表名,第三个参数和第四个参数对应SQL语句中的where(少年,去回顾一下sql语句吧)。

但Cursor还不不太利于我们的操作,来粉饰一波,在Cursor外面再裹上一层,因此就有了CursorWrapper,创建一个类让它继承CursorWrapper:

public class NoteCursorWrapper extends CursorWrapper {
    public NoteCursorWrapper(Cursor cursor){
        super(cursor);
    }
}

变更刚刚的queryNote方法:

private CursorWrapper queryNote(String whereClause, String[] whereArgs){

Cursor cursor = database.query(
        NoteDbScheme.NoteTable.name,
        null,
        whereClause,
        whereArgs,
        null,
        null,
        null
);
return new CursorWrapper(cursor);

}
之所以说CursorWrapper方便,是因为我们将查询返回的Cursor传入该类之后,可以很方便的把查询的值取出来,还是看代码:
......
......

六 后记

别看了,太晚了,写之前没料到会有这么长。明天继续写。


aristark
305 声望17 粉丝

If you ignore it, you’re likely to get bitten.