任务5-1:运用SQLite数据库存储和访问数据

5.1 创建数据访问对象类

数据访问对象即Data Access Object(缩写为 DAO )。它向需要访问数据的模块提供接口调用,而隐藏数据库操作的具体细节。
虽然在UI代码中插入数据库操作代码并不会引起语法错误,但是从软件架构设计的角度来考虑,进行层次化的设计,更有利于使软件结构清晰,便于阅读、理解和维护。

步骤1:在我们应用程序的包下创建名为dao的子包:

步骤2:在dao包中创建名为NoteDAO数据访问对象类:

package com.jing.app.sn.dao;

public class NoteDAO {
    
}

步骤3:为NoteDAO类添加一个Context类型属性——后面的数据库操作需要这个对象:

public class NoteDAO {
    private Context context;

}

步骤4:将NoteDAO类改写成单例模式

按照之前学习过的单例模式实现方法,将NoteDAO类设置为单例:

public class NoteDAO {

    /**
     * 采用单例模式
     */
    private static NoteDAO sInstance;
    
    public static NoteDAO getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new NoteDAO(context);
        }
        return sInstance;
    }

    private Context context;

    /**
     * 私有构造方法
     * @param context
     */
    private NoteDAO(Context context) {
        this.context = context;
    }
}

可以注意到,这次单例模式的实现中,稍有不同的是getInstance()方法多了一个Context类型的参数,这在后面的编码中要用到。

5.2 创建SQLiteOpenHelper子类

SQLiteOpenHelper类的基础上扩展(extends)我们自己的子类NoteDbHelper,通过它来创建、维护和获取我们的数据库对象。

从软件设计的角度考虑,我们没有必要在NoteDAO类之外的地方使用SQLiteOpenHelper对象。因此考虑将NoteDbHelper类定义为NoteDAO类的非公有静态内部类。

在NoteDAO类的内部创建静态(static)类NoteDbHelper并继承SQLiteOpenHelper类,然后添加必要构造方法并重写两个回调方法:

public class NoteDAO {
    ...
    static class NoteDbHelper extends SQLiteOpenHelper {

        public NoteDbHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
            super(context, name, factory, version);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {

        }

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

        }
    }
}

对于NoteDbHelper类的构造方法,我们主要关注它的两个参数:

  • name: 数据库文件名。我们自定一个文件名提供给构造方法。
  • version: 数据库版本。如果在开发过程中要对数据库进行修改,如新建、删除数据表或修改数据表结构,则应提供大于当前版本的数值。

为NoteDAO类添加NoteDbHelper成员变量

NoteDAO类需要持有一个NoteDbHelper类对象,以便通过它来访问数据库。同时,要在NoteDAO类的构造方法中对其分配内存并设置参数。
找到NoteDAO类的构造方法,将其改写为如下形式:

    private NoteDAO(Context context) {
        this.context = context;
        dbHelper = new NoteDbHelper(context, "note.db", null, 1);
    }

当创建NoteDbHelper类对象时,构造方法被调用。此时:

  • 如果name参数指定的数据库文件不存在,就创建这个文件,并且调用onCreate()回调方法;
  • 如果version参数给出的版本号大于当前版本号,则调用onUpgrade()回调方法;

至于两个回调方法具体做什么,要由我们自己来定义。下面我们重点关注onCreate()回调方法。

5.3 创建note表

此时,在onCreate()中,我们只需要将note表创建起来。根据数据库设计,需要执行以下的SQL语句:

CREATE TABLE note (
id INTEGER PRIMARY KEY AUTOINCREMENT, 
title TEXT, 
content TEXT, 
createTime INTEGER
)

理论上,我们可以直接将这个SQL语句作为字符串参数,传递给SQLiteDatabase类的execSQL()方法来执行。但是,其中涉及到的表名("note")列名("id","title","content","createTime")除了用在这里,在之后的所有数据库访问操作中也仍然会经常使用。比较方便的做法是将它们定义为常量,这样便于引用。同样的道理,我们将数据库文件名称、版本号也定义为常量,以便统一管理维护。

创建常量文件

在包dao中创建名为Constants.java的源文件作为常量存放位置:

打开Constants.java,在其中定义各个常量:

public class Constants {
    // 数据库文件名
    public static final String DB_NAME = "note.db";

    // 数据库版本号
    public static final int VERSION = 1;

    // note表名字
    public static final String TABLE_NOTE = "note";

    // note表各列名字
    public static final String COL_ID = "_id";
    public static final String COL_TITLE = "title";
    public static final String COL_CONTENT = "content";
    public static final String COL_CREATE_TIME = "createTime";
}

于是,上面的建表SQL语句可以改写为:

"CREATE TABLE " + TABLE_NOTE + "(" +
    COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
    COL_TITLE + " TEXT, " +
    COL_CONTENT + " TEXT, " +
    COL_CREATE_TIME + " INTEGER)"

接下来,回到NoteDAO.java文件,在文件上部众多的"import ......"语句之后,添加如下语句以将这些常量导入(xxx.xxx.xx部分替换成你自己的应用程序包名):

import static xxx.xxx.xxx.dao.Constants.*;

这样就可以直接引用这些常量了。

用常量数据库名和版本号替换硬编码(hard code)

找到NoteDAO类的构造方法。在前面,我们已经在这里创建了NoteDbHelper类的实例:

    private NoteDAO(Context context) {
        this.context = context;
        dbHelper = new NoteDbHelper(context, "note.db", null, 1);
    }

用刚才定义的常量替换数据库文件版本两个参数,变成如下形式:

    private NoteDAO(Context context) {
        this.context = context;
        dbHelper = new NoteDbHelper(context, DB_NAME, null, VERSION);
    }

编写创建note表代码

回到NoteDbHelper类onCreate()方法,添加创建note表相关的代码:

db.execSQL("CREATE TABLE " + TABLE_NOTE + "(" +
            COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            COL_TITLE + " TEXT, " +
            COL_CONTENT + " TEXT, " +
            COL_CREATE_TIME + " INTEGER)");

编写单元测试用例

由于我们目前还没有将数据库与应用程序主体部分对接,因此很难立即对刚才编写的数据库模块进行验证。可以运用Android的单元测试框架来解决这个问题。

首先我们为了测试方便,在NoteDAO类的构造函数下面增加如下的方法:

    NoteDbHelper getDbHelper() {
        return dbHelper;
    }

这个方法让同在dao包下的某个类可以获取到dbHelper对象。

接下来,找到名为androidTest的目录:

可以看到,androidTest下有一个和我们应用程序同名的包。在这个包下同样创建一个名为dao的子包,并在里面添加测试类NoteDAOTest

NoteDAOTest类编写测试代码如下:

@RunWith(AndroidJUnit4.class)
public class NoteDAOTest {
    @Test
    public void testCreateDatabase() throws Exception {
        Context context = InstrumentationRegistry.getTargetContext();
        NoteDAO dao = NoteDAO.getInstance(context);
        NoteDAO.NoteDbHelper dbHelper = dao.getDbHelper();

        // 取只读的数据库对象
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        // 检查数据库对象是否获取成功
        assertNotNull(db);
        // 对note表进行一次查询
        Cursor cursor = db.query(Constants.TABLE_NOTE, null, null, null, null, null, null);
        // 检查查询操作是否成功
        assertNotNull(cursor);
    }
}

然后右键单击NoteDAOTest类,在菜单中选择“Run 'NoteDAOTest'”并执行。如果测试通过,在Android Studio窗口下方将显示如下的信息:

整个单元测试执行流程如下:

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