DataBase in Android

DataBase in Android

我们知道, 如果我们的数据仅仅是存储在变量中的, 那么其生命周期可能只是和 Activity 一样长, 在我们推出 APP 之后, 我们的数据就会丢失. 我们需要数据持久化(data persistence). 我们可以将数据存储在文件或者数据库中. 这里我们讨论的是数据库的方式.

计算机存储

计算机有两大主要的存储设备, 一类是内存, 一类是外存. 内存(Memory), 是一种临时存储器, 读写速度快, 断电数据丢失, 所以不能长期保存数据. 外存, 包括硬盘, 软盘, 光盘等等, 读写速度慢, 但是可以长期保存数据, 断电不丢失. 我们平时所称的安装 APP, 是安装在外存中, 只有当程序运行的时候, 才会被加载到内存中(也必须被加载到内存中才能运行). 我们在程序中使用的变量, 他们的生命周期是随着程序的结束而结束的, 在程序启动时被创建和使用, 在程序结束时被系统销毁, 所以不能作为长久保存数据的工具.

Android 中的几种不同的数据存储选择

我们这里所讨论的内容, 是将数据存储到我们的 Android 设备中, 不包括存储到云端等方式.

  • Files, 文件
  • Shared Preferences
  • SQLite Databases, SQLite 数据库

Files, 保存到文件中

将保存为文件, 常见的有图片, 音乐, 视频的存储等.

Shared Preferences(首选项)

数据以键值对的形式来保存, (Key - Unique string, Value - Primitive types and Strings), 即包括一个唯一的字符串 key 和其对应的值, 值可以是各种原始数据类型或者是 String.

Shared Preferences 不适合存储大量的用户数据, 适合存储少量的关键的用户信息.

SQLite Databases

SQLLite 是一种数据库, 数据库简单来讲, 是一种有组织的数据结构.

SQLite 的基本组成简单来讲, 可以理解为表, SQLite 数据库由一张张的表来组成.

数据库可以让我们轻松的分享大量相关的结构化数据, 同时, 这些数据可能随着使用而不断增长. 比如我们的通讯录, 里面存储的每一个联系人的各种信息, 可以理解为一张表, 记录各种的属性, 比如姓名, 手机, 邮箱等等. 这些联系人之间, 属性和属性之间是相关的, 他们都是大致相同的格式化数据. 属性之间是相关的, "张三" 的手机号是 "130XXXXXXXX" 而不是其他. 同时, 通讯录还会因为我们的联系人的增多而添加内容, 还可以修改和删除内容. 对于这些数据, 我们可以轻松的检索到我们需要的信息.

SQLite

安装 SQLite

最新版的 MacOS 中已经预装了 SQLite, 打开终端输入 sqlite3, 可以检查是否有 SQLite.

SQL

当我们输入 sqlite3 命令进入 SQLite 之后会出现一下界面.

dcrdeMacBook-Pro:~ Dcr$ sqlite3
SQLite version 3.16.0 2016-11-04 19:09:39
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite>

这里我们可以使用 SQL(Structured query language, 结构化查询语言).

SQLite 中的类型

SQLite 中的 Storage Class:

  • NULL
  • INTEGER: 有符号整数
  • REAL: 浮点数
  • TEXT: 字符串
  • BLOB: 原始数据, 比如图片或者二进制数据.

对于 booleans, 我们可以使用 INTEGER 来代替:

  • 0 - false
  • 1 - true

语法

下面介绍 SQL 的基本语法.

CRUD

CRUD 是 Create, Read, Update, Delete 的缩写. 是数据库中的四项重要操作.

CREATE
Create a database

在 SQLite 中, 创建一个新的数据库, 可以在命令行中使用 sqlite3 <database_name>.db 来创建. 或者在进入 sqlite 之后, 使用 .open <database_name>.db 来创建.

Create a table

创建一张新的表需要指定表名和列名和对应的数据类型. 语法如下:

CREATE TABLE <table_name>(
    <column_name_1> <data_type_1>,
    <column_name_2> <data_type_2>,
    ...
);

上面是创建表的基本语法, 我们创建时, 还可以给每列对应的参数添加属性, 基本的属性有:

  • PRIMARY KEY: 主键, 每张表只能有一个主键.
  • AUTOINCREMENT: 添加行时, 该属性自动加一, 可以用于指定每行的 ID.
  • NOT NULL: 非空, 表示添加行时, 该元素必须有值.
  • DEFAULT <value>: 默认值, 如果创建时没有提供值, 那么以默认值填充.
INSERT data in a table

向表中添加行:

INSERT INTO <table_name> (
    <column_name_1>,
    <column_name_2>,
    ...) VALUES (
    <values_1>,
    <values_2>,
    ...
);
READ

读操作使用的是 SELECT 关键字, 基本操作如下:

SELECT <columns>
FROM <table_name>;

SELECT 后跟的列项可以用 * 表示所有列.

执行上面的操作, 是查询一个表的所有行, 我们要查询我们所需要的行时, 可以使用 WHERE 关键字.

SELECT <columns>
FROM <table_name>
WHERE <condition>;

我们还可以指定结果的排序方式, 通过 ORDER BY 关键字.

SELECT <columns>
FROM <table_name>
WHERE <condition>
ORDER BY <ASC|DESC>
  • ASC: 升序
  • DESC: 降序
UPDATE

UPDATE 用于更新表中的数据.

UPDATE <table_name>
SET <column_name> = <value>
WHERE <condition>;

同样, 使用 WHERE 指定所要修改的行.

DELETE
删除行

删除满足 WHERE 条件的行.

注意: 使用 DELETE 一定要非常小心, 比如, DELETE FROM table_name 就会删除所有行.

DELETE FROM <table_name>
WHERE <condition>;
删除表

删除表使用 DROP TABLE 关键字:

DROP TABLE <table_name>;

SQLite 中的命令

  • .shema <table_name>: 显示创建表的命令.
  • PRAGMA TABLE_INFO(<table_name>);: 显示表的列信息

SQLite in Android

Why is a database always represented with a cylinder?

确定数据库的架构

  1. 确定表的名称
  2. 每一列数据的名称和数据类型

TABLE_NAME = "pets"

Column name Data types Attributes
_id INTEGER PRIMARY KEY AUTOINCREEMENT
name TEXT NOT NULL
breed TEXT
gender INTEGER NOT NULL DEFAULT 0
weight INTEGER NOT NULL DEFAULT 0

Contract

我们在使用数据库时, 会用到很多的变量, 为了方便和不出错, 我们可以将这些变量单独拿出来, 这就是 Contract 类.

Contract 类中, 定义了我们将用到的许多常量, 比如, URI, 表名, 列名以及一些有特殊意义的整数值等等. 下面是一个简单的例子: 例子中, 重写了私有的构造函数, 因为我们并不需要该类的对象.

public final class FeedReaderContract {
    // To prevent someone from accidentally instantiating the contract class,
    // make the constructor private.
    private FeedReaderContract() {}

    /* Inner class that defines the table contents */
    public static class FeedEntry implements BaseColumns {
        public static final String TABLE_NAME = "entry";
        public static final String COLUMN_NAME_TITLE = "title";
        public static final String COLUMN_NAME_SUBTITLE = "subtitle";
    }
}

有了 Contract 类之后, 我们就可以写出我们常用的 SQL 语句了. 比如, 下面就是我们常用的 CREATE TABLEDROP TABLE 语句.

private static final String SQL_CREATE_ENTRIES =
    "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
    FeedEntry._ID + " INTEGER PRIMARY KEY," +
    FeedEntry.COLUMN_NAME_TITLE + " TEXT," +
    FeedEntry.COLUMN_NAME_SUBTITLE + " TEXT)";

private static final String SQL_DELETE_ENTRIES =
    "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;

通过 SQLHelper 创建数据库

在 Android 中, 数据库默认存储在应用的私有区域的, 这一区域默认情况下其他应用是无法访问的, 所以是安全的.

我们可以使用 SQLiteOpenHelper 类来获取数据库的引用, 此时, 系统会在需要的时候才会执行 creating 和 update 数据库这些费时的操作, 而不是在应用启动的时候, 我们需要做的就是去调用 getWritableDatabase() 或者 getReadableDatabase() 方法.

注意: 因为这样的操作可能是长时间操作, 所以, 务必请在后台线程调用 getWritableDatabase() 或者 getReadableDatabase() 方法.

使用时, 我们可以继承 SQLiteOpenHelper 类重写 onCreate(), onUpgrade(), 和 onOpen() 回调方法, 在需要时, 也可以重写 onDowngrade() 方法. 比如:

public class FeedReaderDbHelper extends SQLiteOpenHelper {
    // If you change the database schema, you must increment the database version.
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "FeedReader.db";

    public FeedReaderDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_ENTRIES);
    }
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // This database is only a cache for online data, so its upgrade policy is
        // to simply to discard the data and start over
        db.execSQL(SQL_DELETE_ENTRIES);
        onCreate(db);
    }
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        onUpgrade(db, oldVersion, newVersion);
    }
}

我们在需要数据库的时候, 使用下面的语句.

FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());

在数据库中存入数据

我们在将数据存入数据库中时, 可以使用 ContentValues 对象来进行.

ContentValues 对象就是键值对组, 操作起来也比较简单. 操作步骤如下:

// 1. 获取 SQLiteDatabase 对象
SQLiteDatabase db = mDbHelper.getWritableDatabase();

// 2. 创建值的 map, keys 是列名
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle);

// 3. 加入到数据库中去
long newRowId = db.insert(FeedEntry.Table_name, null, values);

从数据库中读取数据

method query()

首先我们可以先了解一下 query() 方法.

SQLiteDatabase.query():

Cursor query (String table,
                String[] columns,
                String selection,
                String[] selectionArgs,
                String groupBy,
                String having,
                String orderBy,
                String limit)
Parameters
table String: 要查询的表
column String[]: 要查询的列的数组。
selection String: 对应 SQL 中的 WHERE 语句部分(不包括 WHERE 本身).
selectionArgs String[], 在 selection 参数中, 可能包含 ? 作为参数的占位符, 在这个参数中给出.
groupBy String: 对应 SQL 语句中的 GROUP BY 语句(不包括 GROUP BY 本身).
having String: 对应 HAVING 语句(不包括 HAVING 本身).
orderBy String: SQL ORDER BY 语句(不包括 ORDER BY 本身)
limit String: 返回的行数, LIMIT 分句的格式
SQLiteDatabase db = mDbHelper.getReadableDatabase();

// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
    FeedEntry._ID,
    FeedEntry.COLUMN_NAME_TITLE,
    FeedEntry.COLUMN_NAME_SUBTITLE
    };

// Filter results WHERE "title" = 'My Title'
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };

// How you want the results sorted in the resulting Cursor
String sortOrder =
    FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";

Cursor cursor = db.query(
    FeedEntry.TABLE_NAME,                     // The table to query
    projection,                               // The columns to return
    selection,                                // The columns for the WHERE clause
    selectionArgs,                            // The values for the WHERE clause
    null,                                     // don't group the rows
    null,                                     // don't filter by row groups
    sortOrder                                 // The sort order
    );

Cursor

我们的数据库查询语句返回一个 Cursor 对象, 那么什么是 Cursor 呢? Cursor 是查询结果中行的集合.

首先, 顾名思义, Cursor 有一个 position 的指针, 指向的就是当前的行. 行的编号是从 0 开始的, 初始 position = -1.

其工作模式就是, 通过一系列 move 方法, 来选中行, 再通过不同的 get 方法来获取我们需要的数据.

在确定列的时候, 我们需要知道列名, 然后通过 getColumnIndex() 或者 getColumnIndexOrThrow() 来获取 column index. 有了 column index, 我们就可以通过 getInt() getFloat() 等一系列方法来获取具体的数据.

  • getCount(): 返回 cursor 的行数.
  • getType(int columnIndex): 返回给定的列的类型
  • getDouble(int columnIndex) getFloat(int columnIndex) getInt(int columnIndex) getLong(int columnIndex) getString(int columnIndex) getShort(int columnIndex): 返回对应类型的值.

所有的 move 方法返回一个 boolean 值, 表示能否移动到该行.

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

推荐阅读更多精彩内容