Android学习路线_入门篇(十)数据持久化之数据库

数据持久化是保证数据在程序销毁、设备关机后仍然不会丢失的操作,Android系统上主要有三种方式。最后一个就是数据库了,平时会涉及到很多同类型的数据需要以表的形式保存在本地,比如用户列表、消息列表、历史记录表等等同一类组成属性一致的数据,这些数据就非常适合用数据库进行。

本文已收录至☞Android学习路线_梳理
上一篇☞Android学习路线_入门篇(九)数据持久化之文件读写
下一篇☞Android学习路线_入门篇(十一)网络请求的基本实现

1 Android中的数据库

1.1 数据库基本概念

数据库是“按照数据结构来组织、存储和管理数据的仓库”。是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。 ——《百度百科》

如果正在看这篇博客的你没学过计算机相关专业,我希望你在看完本文之后可以再去了解一下数据库、SQL语法相关的知识,这些前置知识对于本文的理解有帮助作用,并且在程序员的生涯中是必不可少的知识。如果你在阅读过程中感觉晦涩,可以先去学完相关知识之后再回来阅读。

如果你学过计算机相关专业,相信你对数据库已经有了一些了解(数据库原理相关课程日常翘课的当我没说T_T)。

数据库主要分为关系型数据库和非关系型数据库:

  1. 关系型数据库
    存储的格式可以直观地反映实体间的关系。关系型数据库和常见的表格比较相似,关系型数据库中表与表之间是有很多复杂的关联关系的。 常见的关系型数据库有Mysql,SqlServer等,大多数都遵循SQL(结构化查询语言,Structured Query Language)标准。 常见的操作有查询,新增,更新,删除,求和,排序等。
  2. 非关系型数据库(NoSql)
    随着近些年技术方向的不断拓展,大量的NoSql数据库如MongoDB(文档数据库)、Redis(键值对存储)、InfoGrid(图形数据库)、Hbase(列存储)出于简化数据库结构、避免冗余、影响性能的表连接、摒弃复杂分布式的目的被设计,但目前在移动端并无较大成果,主要是分布式的、非关系型的、不保证遵循ACID原则的数据存储系统。

1.2 SQLite简介

Android系统中使用的数据库系统是SQLite,这是一个轻量级的关系型数据库,对SQL的支持不会逊色于其他开源数据库,性能也不差,麻雀虽小五脏俱全说的就是它。

SQLite功能特性:

  1. ACID事务
  2. 零配置 – 无需安装和管理配置
  3. 储存在单一磁盘文件中的一个完整的数据库
  4. 数据库文件可以在不同字节顺序的机器间自由的共享
  5. 支持数据库大小至2TB
  6. 足够小, 大致13万行C代码, 4.43M
  7. 比一些流行的数据库在大部分普通数据库操作要快
  8. 简单, 轻松的API
  9. 包含TCL绑定, 同时通过Wrapper支持其他语言的绑定
  10. 良好注释的源代码, 并且有着90%以上的测试覆盖率
  11. 独立: 没有额外依赖
  12. 源码完全的开源, 你可以用于任何用途, 包括出售它
  13. 支持多种开发语言,C, C++, PHP, Perl, Java, C#,Python, Ruby等

在Android中,SQLite支持9种数据类型的存储:

123456789
ByteLongShortIntegerFloatDoubleStringBooleanbyte[]

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数据库框架,排名不分先后:

  1. OrmLite
    简述: 轻量级,使用简单,易上手,封装完善,文档全面,基于反射,效率较低,缺少中文翻译文档。
    地址:http://ormlite.com/

  2. LitePal
    简述:郭霖大神开源的数据库框架,他的博客也比较详细的介绍了其用法。近乎零配置,入门极快。
    地址:https://github.com/guolindev/LitePal

  3. GreenDao
    简述:存取速度快,支持数据库加密,轻量级,激活实体,支持缓存,代码自动生成。
    地址:https://github.com/greenrobot/greenDAO

  4. DBFlow
    简述:存取速度快,支持数据库加密,轻量级,激活实体,支持缓存,代码自动生成。
    地址:https://github.com/agrosner/DBFlow

  5. Realm
    简述:不基于SQLite,速度极快,跨平台,支持JSON,可视化。
    地址:
    java版:https://github.com/realm/realm-java
    kotlin版:https://github.com/realm/realm-kotlin

  6. Room
    简述:谷歌官方推出,MVVM框架Jetpack的一员,支持kotlin data class。
    地址:https://developer.android.google.cn/training/data-storage/room?hl=zh-cn

完毕

今天的分享就到这里,文章多有不足,各位小伙伴有什么想法可以直接评论或是私信,要是对你有所帮助就给我一个赞吧,喜欢我的小伙伴可以关注我哦~

本文已收录至☞Android学习路线_梳理
上一篇☞Android学习路线_入门篇(九)数据持久化之文件读写
下一篇☞Android学习路线_入门篇(十一)网络请求的基本实现

支持我的小伙伴们可以微信搜索“Android思维库”,或者微信扫描下方二维码,关注我的公众号,每天都会推送新知识~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值