数据持久化是保证数据在程序销毁、设备关机后仍然不会丢失的操作,Android系统上主要有三种方式。最后一个就是数据库了,平时会涉及到很多同类型的数据需要以表的形式保存在本地,比如用户列表、消息列表、历史记录表等等同一类组成属性一致的数据,这些数据就非常适合用数据库进行。
本文已收录至☞Android学习路线_梳理
上一篇☞Android学习路线_入门篇(九)数据持久化之文件读写
下一篇☞Android学习路线_入门篇(十一)网络请求的基本实现
1 Android中的数据库
1.1 数据库基本概念
数据库是“按照数据结构来组织、存储和管理数据的仓库”。是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。 ——《百度百科》
如果正在看这篇博客的你没学过计算机相关专业,我希望你在看完本文之后可以再去了解一下数据库、SQL语法相关的知识,这些前置知识对于本文的理解有帮助作用,并且在程序员的生涯中是必不可少的知识。如果你在阅读过程中感觉晦涩,可以先去学完相关知识之后再回来阅读。
如果你学过计算机相关专业,相信你对数据库已经有了一些了解(数据库原理相关课程日常翘课的当我没说T_T)。
数据库主要分为关系型数据库和非关系型数据库:
- 关系型数据库
存储的格式可以直观地反映实体间的关系。关系型数据库和常见的表格比较相似,关系型数据库中表与表之间是有很多复杂的关联关系的。 常见的关系型数据库有Mysql,SqlServer等,大多数都遵循SQL(结构化查询语言,Structured Query Language)标准。 常见的操作有查询,新增,更新,删除,求和,排序等。 - 非关系型数据库(NoSql)
随着近些年技术方向的不断拓展,大量的NoSql数据库如MongoDB(文档数据库)、Redis(键值对存储)、InfoGrid(图形数据库)、Hbase(列存储)出于简化数据库结构、避免冗余、影响性能的表连接、摒弃复杂分布式的目的被设计,但目前在移动端并无较大成果,主要是分布式的、非关系型的、不保证遵循ACID原则的数据存储系统。
1.2 SQLite简介
Android系统中使用的数据库系统是SQLite,这是一个轻量级的关系型数据库,对SQL的支持不会逊色于其他开源数据库,性能也不差,麻雀虽小五脏俱全说的就是它。
SQLite功能特性:
- ACID事务
- 零配置 – 无需安装和管理配置
- 储存在单一磁盘文件中的一个完整的数据库
- 数据库文件可以在不同字节顺序的机器间自由的共享
- 支持数据库大小至2TB
- 足够小, 大致13万行C代码, 4.43M
- 比一些流行的数据库在大部分普通数据库操作要快
- 简单, 轻松的API
- 包含TCL绑定, 同时通过Wrapper支持其他语言的绑定
- 良好注释的源代码, 并且有着90%以上的测试覆盖率
- 独立: 没有额外依赖
- 源码完全的开源, 你可以用于任何用途, 包括出售它
- 支持多种开发语言,C, C++, PHP, Perl, Java, C#,Python, Ruby等
在Android中,SQLite支持9种数据类型的存储:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|
Byte | Long | Short | Integer | Float | Double | String | Boolean | byte[] |
2 数据库基本操作
2.1 数据库创建
Android原生操作数据库需要通过SQLiteOpenHelper
类,这是一个抽象类,使用时需要新建一个类继承SQLiteOpenHelper
类,并且实现其抽象方法onCreate()
和onUpgrade()
。
我们假设要开一家书店,需要用数据库来管理书店里的书籍数据。首先,新建类MyDatabaseHelper
,继承SQLiteOpenHelper
,实现构造方法MyDatabaseHelper()
及抽象方法onCreate()
和onUpgrade()
:
public class MyDatabaseHelper extends SQLiteOpenHelper {
private Context mContext;
public MyDatabaseHelper(Context context, String name,
SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
//第一次创建数据库时会调用此方法
@Override
public void onCreate(SQLiteDatabase db) {
}
//数据库版本升级时会调用此方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
这样就可以正常使用数据库了,那么如何使用呢?接下来我们在MainActivity中new一个MyDatabaseHelper
对象并调用getReadableDatabase( )
或者进行数据库的创建getWritableDatabase( )
,(两者不同点在于,当数据库不可写入时(如磁盘空间已满)getReadableDatabase( )方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase( )方法则将出现异常)
public class MainActivity extends AppCompatActivity {
//数据库文件名称
private static final String DATABASE_NAME = "BookStore.db";
//数据库版本号
private static final int DATABASE_VERSION = 1;
private MyDatabaseHelper mMyDatabaseHelper;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMyDatabaseHelper = new MyDatabaseHelper(this, DATABASE_NAME, null, DATABASE_VERSION);
//MyDatabaseHelper的onCreate()和onUpgrade()方法会在符合条件时自动执行
mMyDatabaseHelper.getReadableDatabase();
}
}
先别急着运行,你的数据库虽然可以创建了,但是里面可还没有办法存数据。我们还缺少一张表,先来创建一张Book
表吧。还记得SQL语句吗?创建表格需要完全使用SQL语句哦,这张Book
表的创建SQL语句如下:
public class MyDatabaseHelper extends SQLiteOpenHelper {
//用于创建Book表的SQL语句
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text, "
+ "catagory_id integer)";
...
}
然后在onCreate()
方法中插入一条语句:
public class MyDatabaseHelper extends SQLiteOpenHelper {
...
//第一次创建数据库时会调用此方法
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
}
...
}
很好,这时候数据库的创建就真正结束了,并且创建了一张名为Book
的数据表,你可以运行一下。
当然表面上看不出任何成果,还是跟我继续看看之后要做些什么吧。
2.2 数据库升级
数据库的结构不会是一成不变的,如果有了新的需求,需要增加一张表来存储另一种数据该怎么办呢?onCreate()
方法中再加一句创建表的语句,onUpgrade()
方法中判断老数据库版本并执行创建新表的语句,数据库版本+1。看看具体怎么做吧,我们来创建一个新的Category
表,修改MyDatabaseHelper
类增加创建表Category
的语句:
public class MyDatabaseHelper extends SQLiteOpenHelper {
...
//用于创建Category表的SQL语句
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
...
//第一次创建数据库时会调用此方法
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
//数据库版本升级时会调用此方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
/*第一版的数据库只有Book表,第二版的数据库增加了Category表,
* 为了保证用户体验,在不干扰前一版的数据的情况下,实现对数据库的平滑升级,
* 简单的可以用此方法进行版本升级所需的额外操作*/
switch (oldVersion){
case 1:
db.execSQL(CREATE_CATEGORY);
default:
break;
}
}
...
}
再给数据库版本号+1,修改MainActivity
类:
public class MainActivity extends AppCompatActivity {
...
//数据库版本号
private static final int DATABASE_VERSION = 2;
...
}
很好,我们来执行一下,如果报错崩溃请仔细查看log,寻找错误原因并解决。
这时候可能会有人问了,如果新需求不是新建表而是新增一个字段呢?那么我们需要再修改一下MyDatabaseHelper
类,给Book
表增加一个category_id
字段:
public class MyDatabaseHelper extends SQLiteOpenHelper {
...
//用于给Book表增加category_id字段的SQL语句
public static final String BOOK_ADD_CATEGORY_ID =
"alter table Book add column category_id integer";
...
//数据库版本升级时会调用此方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
/*第一版的数据库只有Book表,第二版的数据库增加了Category表,
* 第三版的数据库在Book表中增加了category_id字段和Category表关联,
* 为了保证用户体验,在不干扰前一版的数据的情况下,实现对数据库的平滑升级,
* 简单的可以用此方法进行版本升级所需的额外操作*/
switch (oldVersion){
case 1:
db.execSQL(CREATE_CATEGORY);
case 2:
db.execSQL(BOOK_ADD_CATEGORY_ID);
default:
break;
}
}
}
同样的数据库版本也要+1,修改MainActivity
类:
public class MainActivity extends AppCompatActivity {
...
//数据库版本号
private static final int DATABASE_VERSION = 3;
...
}
很好,我们再来执行一下,如果报错崩溃请仔细查看log,寻找错误原因并解决。
此时,完整的MyDatabaseHelper
类代码如下:
public class MyDatabaseHelper extends SQLiteOpenHelper {
//用于创建Book表的SQL语句
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text, "
+ "catagory_id integer)";
//用于创建Category表的SQL语句
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
//用于给Book表增加category_id字段的SQL语句
public static final String BOOK_ADD_CATEGORY_ID =
"alter table Book add column category_id integer";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
//第一次创建数据库时会调用此方法
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
//数据库版本升级时会调用此方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
/*第一版的数据库只有Book表,第二版的数据库增加了Category表,
* 第三版的数据库在Book表中增加了category_id字段和Category表关联,
* 为了保证用户体验,在不干扰前一版的数据的情况下,实现对数据库的平滑升级,
* 简单的可以用此方法进行版本升级所需的额外操作*/
switch (oldVersion){
case 1:
db.execSQL(CREATE_CATEGORY);
case 2:
db.execSQL(BOOK_ADD_CATEGORY_ID);
default:
break;
}
}
}
2.3 增
数据库和数据表创建完成以后,数据库还是空空如也的,那么我们先来看看怎么新增一条数据吧。核心方法是insert()
,直译叫插入,所以一般都叫插入数据,简称增。空荡荡的书店进货了,购入了两本书要增加进数据库,具体操作如下:
SQLiteDatabase db = mMyDatabaseHelper.getReadableDatabase();
ContentValues values = new ContentValues();
values.put("name", "The Da Vinci Code");
values.put("author", "Dan Brown");
values.put("pages", 454);
values.put("price", 16.96);
//向数据库插入数据
db.insert("Book", null, values);
/*
如果要继续插入数据,不需要再new ContentValues(),
调用values.clear()后再按上述步骤执行put()方法和insert()方法即可
*/
values.clear();
values.put("name", "The Lost Symbol");
values.put("author", "Dan Brown");
values.put("pages", 510);
values.put("price", 19.96);
db.insert("Book", null, values);
2.4 删
完成了增加数据的操作后,数据库里的Book
表已经存入了两条数据了。可是在经营了一段时间以后我发现大家都不喜欢看太厚的书,于是我想把页数超过500页的书从书店里下架,那么我们怎么把页数超过500的书从数据库里删除呢?具体操作很简单,代码如下:
SQLiteDatabase db = mMyDatabaseHelper.getReadableDatabase();
//删除数据库的数据
db.delete("Book", "pages > ?", new String[]{"500"});
最终执行了这段代码后,"The Lost Symbol"这本书因为页数为510是大于500的而被删除了。
2.5 改
书店只有一本书了,开业这么久了还没有人购买,心里慌啊,怎么办?打折促销吧,书的价格要降一些,本来卖16.96的"The Da Vinci Code",价格要改为10.56,数据库里的数据改一改吧,具体操作如下:
SQLiteDatabase db = mMyDatabaseHelper.getReadableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.56);
//更新数据库的数据
db.update("Book", values, "name=?", new String[]{"The Da Vinci Code"});
2.6 查
降价活动开始以后终于有顾客想买书了,但是他不想去书架找,希望我们给他查一下店里书的基本信息,数据库可以给我们提供查询的功能,这里看一下基本的查询Book
表所有数据的方法:
SQLiteDatabase db = mMyDatabaseHelper.getReadableDatabase();
//对数据库表进行查询,会返回游标
Cursor cursor = db.query("Book", null, null, null, null, null, null);
while (cursor.moveToNext()){
String name = cursor.getString(cursor.getColumnIndex("name"));
String author = cursor.getString(cursor.getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
double price = cursor.getDouble(cursor.getColumnIndex("price"));
Log.d("Query BookStore.db", name);
Log.d("Query BookStore.db", author);
Log.d("Query BookStore.db", pages+"");
Log.d("Query BookStore.db", price+"");
}
cursor.close();
数据库的查询操作是非常强大且非常复杂的,这里就不具体介绍了,作为Android开发入门,原生的数据库基本操作有所了解就够了,而且实际开发工作中并不会用到原生操作。
3 对象关系映射库
3.1 原生数据库操作痛点
前面我们已经了解了原生数据库的基本操作,不知道你看下来感觉怎么样呢?
原生操作很繁琐,而且存在大量重复代码,如果需要增加或查询一个有几十个字段的数据,手都废了。
数据表的创建需要使用纯SQL语法,并且是用字符串的形式执行的,没有代码提示和语法检测,不在数据库里运行一遍的话很容易出错。
我们都是面向对象操作的,数据和对象之间的转化也需要大量的底价值代码,对于程序猿来说这些重复性很高的工作必须要复用、自动化。
3.2 对象关系映射是什么
对象关系映射(Object Relational Mapping,简称ORM),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”,将原本的对数据的操作变为对对象的操作。
想象一下,我们有一个Book对象myBook
,要把它存入数据库只需要myBook.insert()
,修改对象的属性后要更新数据库只需要myBook.update()
,从数据库获取另一个Book对象只需要BookDao.get(BookDao.bookName, "书名")
,从数据库里删除对象只需要myBook.delete()
。
当然,上述用法只是一个假设,但已经有许多三方库有了类似的效果,集成也不复杂,开发效率可以提升很多。
3.3 三方库“巨轮的肩膀”
下面给大家列一下比较知名的ORM数据库框架,排名不分先后:
-
OrmLite
简述: 轻量级,使用简单,易上手,封装完善,文档全面,基于反射,效率较低,缺少中文翻译文档。
地址:http://ormlite.com/ -
LitePal
简述:郭霖大神开源的数据库框架,他的博客也比较详细的介绍了其用法。近乎零配置,入门极快。
地址:https://github.com/guolindev/LitePal -
GreenDao
简述:存取速度快,支持数据库加密,轻量级,激活实体,支持缓存,代码自动生成。
地址:https://github.com/greenrobot/greenDAO -
DBFlow
简述:存取速度快,支持数据库加密,轻量级,激活实体,支持缓存,代码自动生成。
地址:https://github.com/agrosner/DBFlow -
Realm
简述:不基于SQLite,速度极快,跨平台,支持JSON,可视化。
地址:
java版:https://github.com/realm/realm-java
kotlin版:https://github.com/realm/realm-kotlin -
Room
简述:谷歌官方推出,MVVM框架Jetpack的一员,支持kotlin data class。
地址:https://developer.android.google.cn/training/data-storage/room?hl=zh-cn
完毕
今天的分享就到这里,文章多有不足,各位小伙伴有什么想法可以直接评论或是私信,要是对你有所帮助就给我一个赞吧,喜欢我的小伙伴可以关注我哦~
本文已收录至☞Android学习路线_梳理
上一篇☞Android学习路线_入门篇(九)数据持久化之文件读写
下一篇☞Android学习路线_入门篇(十一)网络请求的基本实现
支持我的小伙伴们可以微信搜索“Android思维库”,或者微信扫描下方二维码,关注我的公众号,每天都会推送新知识~