Android数据库高手秘籍

Android数据库高手秘籍(零)——前言

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/38083103

在我刚开始接触Android的时候甚至都不敢相信,Android系统竟然是内置了数据库的!好吧,是我太孤陋寡闻了。由于我之前是从Web方向转过来的,对数据库方面的技术也是特别有偏好,当知道这一消息之后确实颇为惊喜。Android中内置的数据库是SQLite,而我之前做Web开发时最常用的数据库是MySQL,经过对比之后我发现这两个数据库极为相似,都支持标准的SQL语法,还遵循了数据库的ACID事务,所以只要你以前使用过其它的关系型数据库,就可以很快地上手SQLite。

然而我相信还是有不少Android程序员并没有将数据库编程掌握得非常娴熟,或者还只是处于比较简单的使用阶段,这主要是因为我们做客户端的程序员接触数据库的机会要比服务器端的程序员少得多。但是,能够很好地掌握数据库各方面的使用技巧,对于一个Android程序员来说仍然是至关重要的,因此,这里我准备写一个《Android数据库高手秘籍》的专栏,带你一步步成为一个Android数据库高手,这是本专栏的第零篇文章。

由于是高手秘籍嘛,本专栏中的内容主要还是讲解高级技术的。其中包括了Android数据库中一些不为人知的使用技巧,以及全面剖析 LitePal 这款Android数据库框架的所有用法。但同时为了考虑初学者的阅读,里面也会涉及一些基础知识的讲解,总体内容还是难易结合,适合循序渐进学习的。

那么话不多说,我们马上进入到本专栏第一篇文章 Android数据库高手秘籍(一)——SQLite命令 的学习当中。

Android数据库高手秘籍(一)——SQLite命令

要想熟练地操作任何一个数据库,最最基本的要求就是要懂SQL语言,这也是每个程序员都应该掌握的技能。虽说SQL博大精深,要想精通确实很难,但最基本的一些建表命令,增删改查,大家还是必须要学会的。

SQL(Structured Query Language)是一种标准的数据库查询语言,即所有的关系型数据库都会支持它,只不过每种数据库对SQL语言的支持与标准存在着细微的不同。我们无须关心其它数据库对SQL语言的支持情况,这里我们只要把重点放在SQLite上就可以了。下面我将使用模拟器来对SQLite支持的各种命令进行演示,如果你想用手机的话也可以,但要确保你的手机已经Root,并且包含sqlite3这个命令文件。

首先确保模拟器已经连接上了电脑,然后在命令行输入adb shell进入控制台,如下图所示:


注意#符号表示我们当前已经是超级用户了,如果显示的是$符号,表示当前只是普通用户而已,这时还需要输入su命令切换一下用户身份才行。

有了超级用户权限之后,我们能做的事情就很多了,这里我们先查看一下系统自带的联系人表吧。进入到/data/data目录下,如下图所示:


所有应用程序的本地存储文件都是存放在这个目录下面的。为了要让不同应用程序之间的数据容易区别开来,Android是使用应用程序包名进行分开管理,也就是说每个应用程序的本地存储文件都会存放在自己应用程序包名的那个目录下,这里我们ls一下看看有多少子目录:


OK,确实有很多,毕竟手机上所有的应用程序都在这里。其中,com.android.providers.contacts中存放的就是联系人的相关数据,我们进入到这个目录再ls一下:


可以看到,目前有databases、files、lib和shared_prefs这几个子目录。其中databases肯定是用于存放数据库文件的,files是用于存放普通文本文件的,lib是用于存放so库的,shared_prefs则是用于存放shared文件的。这是Android数据持久化的几种可选方式,对这部分内容不太了解的朋友可以参考《第一行代码——Android》的第六章。

接着进入到databases目录中,再ls:


其中后缀名为journal的文件是日志文件,我们不用管,contacts2.db和profile.db才是真正的数据库文件,可以使用sqlite3命令来打开数据库,如下图所示:


好的,数据库已经打开了,那么我们怎么才能知道当前数据库中有哪些表呢?很简单,.table命令就可以做到了:


哇,竟然有这么多张表!是的,联系人的数据结构非常复杂,很多的数据都是分表存储的。这里我们随便挑一张表,比如说accounts表,如果我想知道这张表中有哪些列应该怎么办呢?在MySQL中可以使用desc accounts这个命令,但SQLite却不认识这个命令,毕竟它们是有差异化的。SQLite中可以使用pragma table_info(TABLE_NAME)这个命令来查看表的数据结构,如下图所示:


可以看到,一共显示了三条结果,表示accounts表中共有三列。但是,所有的字段都缩在了一行里面,并用“|”符号分隔,这样我们很难看出每个字段的含义。很简单,只需要换一种显示模式就行了,比如说line模式就挺不错的。输入.mode line命令切换显示模式,然后重新运行pragma命令,结果如下图所示:


怎么样,这样就清晰多了吧?这三列的列名分别是account_name、account_type和data_set,数据类型都是TEXT(字符串),允许为空,并且都不是主键。好,那我现在想查一查accounts表中的数据呢?这就太简单了,使用select语句就可以了,如下所示:


恩?怎么只有一条空数据啊。貌似模拟器上默认就是这样的,如果你用的是手机的话,这里应该就可以查到真正的数据了。不过没关系,我们可以在设置里面手动添加一个邮箱账户,如下图所示:


现在再来重新查询一遍accounts表,如下所示:


OK,添加的新账户已经成功查出来了。

除了查询命令之外,还有其它的增删改命令都和标准的SQL语法是相同的,即insert、delete和update,由于比较简单,我就不再赘述了。比较值得一提的是,每个SQLite数据库中都还有一个隐藏的sqlite_master表,这里记载了当前数据库中所有表的建表语句,可以使用select * from sqlite_master命令进行查看:


结果太多了是不是?一屏根本就显示不下嘛。不要着急,别忘了我们使用的是select命令,可以使用where语句来过滤出我们想要查询的那部分内容,如下图所示:


OK,CREATE TABLE accounts (account_name TEXT, account_type TEXT, data_set TEXT) 这就是accounts表的建表语句了,通过这种方式我们可以查询到任意一张表的建表语句,从而对我们学习和分析数据库表结构有所帮助。

有些朋友可能会觉得,每次都要输入select命令来查询表中的数据太麻烦了。没错,而且还要保证手机是连接在电脑上的时候才能查询,确实太不方便。幸运的是,有些手机软件已经提供了数据库表查询的功能,使得我们随时随地都可以方便地查看数据库中的数据,比如Root Explorer这款软件就不错。

仍然是确保你的手机已经Root,然后安装Root Explorer,打开软件之后按照我们前面介绍的路径,进入/data/data/com.android.providers.contacts/databases,点击contacts2.db数据库,选择内置数据库查看器,然后随便点击一张表就可以查看到里面的数据了,如下图所示:


使用这种方法,我们可以随时查看数据库表中的最新数据,直观又方便,在程序开发的时候可以起到非常大的帮助。

好了,今天的讲解就到这里,下篇文章当中我将带领大家探究Android数据库中更多的奥秘。

 

Android数据库高手秘籍(二)——创建表和LitePal的基本用法

上一篇文章中我们学习了一些Android数据库相关的基础知识,和几个颇为有用的SQLite命令,都是直接在命令行操作的。但是我们都知道,数据库是要和程序结合在一起使用的,单独对一个数据库去进行増删改查操作并没有什么意义,因此今天我们就来学习一下如何在Android程序当中去操作SQLite数据库,还没看过前一篇文章的朋友可以先去参考 Android数据库高手秘籍(一)——SQLite命令 。

操作数据库的第一步当然是创建表了,传统创建表的方法相信大多数人都知道,那么今天我除了会展示传统的建表方法之外,还会讲解LitePal这个框架的基本用法,并使用它来完成同样的建表操作,让大家体会到使用框架来操作数据库的魅力。

那么先来简单介绍一下吧,LitePal是一款开源的Android数据库框架,它采用了对象关系映射(ORM)的模式,并将我们平时开发时最常用到的一些数据库功能进行了封装,使得不用编写一行SQL语句就可以完成各种建表、増删改查的操作。并且LitePal很“轻”,jar包只有100k不到,而且近乎零配置,这一点和Hibernate这类的框架有很大区别。目前LitePal的源码已经托管到了GitHub上,地址是 https://github.com/LitePalFramework/LitePal 。

OK,简单介绍完了LitePal,我们还是先来看一下,在传统的Android开发中,需要怎么去创建表。

传统的建表方式

其实为了方便我们对数据库表进行管理,Android本身就提供了一个帮助类:SQLiteOpenHelper。这个类集创建和升级数据库于一身,并且自动管理了数据库版本,算是一个非常好用的工具。

那我们现在就来试试SQLiteOpenHelper的用法吧。首先你要知道SQLiteOpenHelper是一个抽象类,这意味着如果我们想要使用它的话,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper中有两个抽象方法,分别是onCreate()和onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。本篇文章只需要把注意力放在创建数据库这里就行了,升级数据库我们会在下一篇文章中去讨论。

新建一个MySQLiteHelper类并让它继承SQLiteOpenHelper,这样一个最基本的数据库帮助类的代码如下所示:

[java]  view plain copy
  1. public class MySQLiteHelper extends SQLiteOpenHelper {  
  2.   
  3.     public MySQLiteHelper(Context context, String name, CursorFactory factory,  
  4.             int version) {  
  5.         super(context, name, factory, version);  
  6.     }  
  7.   
  8.     @Override  
  9.     public void onCreate(SQLiteDatabase db) {  
  10.     }  
  11.   
  12.     @Override  
  13.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  14.     }  
  15.   
  16. }  
其中,当数据库创建的时候会调用onCreate()方法,在这里去执行建表操作就可以了。比如说我们想新建一张news表,其中有title,content,publishdate,commentcount这几列,分别代表着新闻标题、新闻内容、发布时间和评论数,那么代码就可以这样写:
[java]  view plain copy
  1. public class MySQLiteHelper extends SQLiteOpenHelper {  
  2.       
  3.     public static final String CREATE_NEWS = "create table news ("  
  4.             + "id integer primary key autoincrement, "  
  5.             + "title text, "  
  6.             + "content text, "  
  7.             + "publishdate integer,"  
  8.             + "commentcount integer)";  
  9.   
  10.     public MySQLiteHelper(Context context, String name, CursorFactory factory,  
  11.             int version) {  
  12.         super(context, name, factory, version);  
  13.     }  
  14.   
  15.     @Override  
  16.     public void onCreate(SQLiteDatabase db) {  
  17.         db.execSQL(CREATE_NEWS);  
  18.     }  
  19.     ...  
  20. }  
可以看到,我们把建表语句定义成了一个常量,然后在onCreate()方法中去执行了这条建表语句,news表也就创建成功了。这条建表语句虽然简单,但是里面还是包含了一些小的细节,我来解释一下。首先,根据数据库的范式要求,任何一张表都应该是有主键的,所以这里我们添加了一个自增长的id列,并把它设为主键。然后title列和content列都是字符串类型的,commentcount列是整型的,这都很好理解,但是publishdate列该怎么设计呢?由于SQLite中并不支持存储日期这种数据类型,因此我们需要将日期先转换成UTC时间(自1970年1月1号零点)的毫秒数,然后再存储到数据库中,因此publishdate列也应该是整型的。

现在,我们只需要获取到SQLiteDatabase的实例,数据库表就会自动创建了,如下所示:

[java]  view plain copy
  1. SQLiteOpenHelper dbHelper = new MySQLiteHelper(this"demo.db"null1);  
  2. SQLiteDatabase db = dbHelper.getWritableDatabase();  

感觉很简单很方便是吗?那你就太容易满足了,下面我们就来学习一下LitePal的基本用法,看一看使用这个框架是如何实现同样的功能的。

LitePal的基本用法

虽说LitePal宣称是近乎零配置,但也只是“近乎”而已,它还是需要进行一些简单配置才可以使用的,那么我们第一步就先快速学习一下LitePal的配置方法。

快速配置

1. 引入Jar包或源码

首先我们需要将LitePal的jar包引入到项目当中,可以点击这里查看LitePal的最新版本,选择你需要的下载即可。下载好了jar包之后,把它复制到项目的libs目录中就算是引入成功了,如下图所示:


如果你不想用jar包的话,也可以把LitePal的源码下载下来,然后作为一个library库导入到Eclipse当中,再让我们的项目去引用这个library库就可以了。

2. 配置litepal.xml

接着在项目的assets目录下面新建一个litepal.xml文件,并将以下代码拷贝进去:

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <litepal>  
  3.     <dbname value="demo" ></dbname>  
  4.   
  5.     <version value="1" ></version>  
  6.   
  7.     <list>  
  8.     </list>  
  9. </litepal>  
配置文件相当简单,<dbname>用于设定数据库的名字,<version>用于设定数据库的版本号,<list>用于设定所有的映射模型,我们稍后就会用到。

3. 配置LitePalApplication

由于操作数据库时需要用到Context,而我们显然不希望在每个接口中都去传一遍这个参数,那样操作数据库就显得太繁琐了。因此,LitePal使用了一个方法来简化掉Context这个参数,只需要在AndroidManifest.xml中配置一下LitePalApplication,所有的数据库操作就都不用再传Context了,如下所示:

[html]  view plain copy
  1. <manifest>  
  2.     <application  
  3.         android:name="org.litepal.LitePalApplication"  
  4.         ...  
  5.     >  
  6.     ...  
  7.     </application>  
  8. </manifest>  
当然,有些程序可能会有自己的Application,并在这里配置过了。比如说有一个MyApplication,如下所示:
[html]  view plain copy
  1. <manifest>  
  2.     <application  
  3.         android:name="com.example.MyApplication"  
  4.         ...  
  5.     >  
  6.     ...  
  7.     </application>  
  8. </manifest>  
没有关系,这时只需要修改一下MyApplication的继承结构,让它不要直接继承Application类,而是继承LitePalApplication类,就可以使用一切都能正常工作了,代码如下所示:
[java]  view plain copy
  1. public class MyApplication extends LitePalApplication {  
  2.     ...  
  3. }  

但是,有些程序可能会遇到一些更加极端的情况,比如说MyApplication需要继承另外一个AnotherApplication,并且这个AnotherApplication还是在jar包当中的,不能修改它的代码。这种情况应该算是比较少见了,但是如果你遇到了的话也不用急,仍然是有解释方案的。你可以把LitePal的源码下载下来,然后把src目录下的所有代码直接拷贝到你项目的src目录下面,接着打开LitePalApplication类,将它的继承结构改成继承自AnotherApplication,再让MyApplication继承自LitePalApplication,这样所有的Application就都可以在一起正常工作了。

仅仅三步,我们就将所有的配置工作全部完成了,并且这是一件一本万利的事情,自此以后,你就可以开心地体验LitePal提供的各种便利了,就让我们从建表开始吧。

开始建表

前面在介绍的时候已经说了,LitePal采取的是对象关系映射(ORM)的模式,那么什么是对象关系映射呢?简单点说,我们使用的编程语言是面向对象语言,而我们使用的数据库则是关系型数据库,那么将面向对象的语言和面向关系的数据库之间建立一种映射关系,这就是对象关系映射了。

但是我们为什么要使用对象关系映射模式呢?这主要是因为大多数的程序员都很擅长面向对象编程,但其中只有少部分的人才比较精通关系型数据库。而且数据库的SQL语言晦涩难懂,就算你很精通它,恐怕也不喜欢经常在代码中去写它吧?而对象关系映射模式则很好地解决了这个问题,它允许我们使用面向对象的方式来操作数据库,从而可以从晦涩难懂的SQL语言中解脱出来。

那么接下来我们就看一看LitePal中是如何建表的吧。根据对象关系映射模式的理念,每一张表都应该对应一个模型(Model),也就是说,如果我们想要建一张news表,就应该有一个对应的News模型类。新建一个News类,如下所示:

[java]  view plain copy
  1. package com.example.databasetest.model;  
  2.   
  3. public class News {  
  4. }  
然后,表中的每一列其实就是对应了模型类中的一个字段,比如news表中有id、title、content、publishdate、commentcount这几个列,那么在News类中就也应该有这几个字段,代码如下所示:
[java]  view plain copy
  1. public class News {  
  2.       
  3.     private int id;  
  4.       
  5.     private String title;  
  6.       
  7.     private String content;  
  8.       
  9.     private Date publishDate;  
  10.       
  11.     private int commentCount;  
  12.       
  13.     // 自动生成get、set方法  
  14.     ...  
  15. }  
其中id这个字段可写可不写,因为即使不写这个字段,LitePal也会在表中自动生成一个id列,毕竟每张表都一定要有主键的嘛。

这里我要特别说明一下,LitePal的映射规则是非常轻量级的,不像一些其它的数据库框架,需要为每个模型类单独配置一个映射关系的XML,LitePal的所有映射都是自动完成的。根据LitePal的数据类型支持,可以进行对象关系映射的数据类型一共有8种,int、short、long、float、double、boolean、String和Date。只要是声明成这8种数据类型的字段都会被自动映射到数据库表中,并不需要进行任何额外的配置。

那么有的朋友可能会问了,既然是自动映射的话,如果News类中有一个字符串字段我并不想让它映射到数据库表中,这该怎么办呢?对此,LitePal同样采用了一种极为轻量的解决方案,只有声明成private修饰符的字段才会被映射到数据库表中,如果你有某一个字段不想映射的话,只需要将它改成public、protected或default修饰符就可以了。

现在模型类已经建好了,我们还差最后一步,就是将它配置到映射列表当中。编辑assets目录下的litepal.xml文件,在<list>标签中加入News模型类的声明:

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <litepal>  
  3.     <dbname value="demo" ></dbname>  
  4.   
  5.     <version value="1" ></version>  
  6.   
  7.     <list>  
  8.         <mapping class="com.example.databasetest.model.News"></mapping>  
  9.     </list>  
  10. </litepal>  
注意这里一定要填入News类的完整类名。

OK,这样所有的工作就都已经完成了,现在只要你对数据库有任何的操作,news表就会被自动创建出来。比如说LitePal提供了一个便捷的方法来获取到SQLiteDatabase的实例,如下所示:

[java]  view plain copy
  1. SQLiteDatabase db = Connector.getDatabase();  
调用一下上述代码,news表就应该已经创建成功了。我们使用在上一篇文章中学到的SQLite命令来查看一下,打开demo.db数据库,输入.table命令,结果如下图所示:


可以看到,news表已经存在了。另外两张android_metadata和table_schema表是自动生成的,我们不用理。接下来我们还可以再查询一下news表的建表语句,如下图所示:


这就是LitePal根据News类中的字段自动帮我们生成的建表语句,由此也说明,建表操作已经成功完成了。

好了,到目前为止你已经算是对LitePal的用法有点入门了,那么本篇文章的内容就到这里,下篇文章当中我们将学习使用LitePal进行升级表的操作。

Android数据库高手秘籍(三)——使用LitePal升级表

在上一篇文章中,我们学习了LitePal的基本用法,体验了使用框架来进行创建表操作的便利。然而大家都知道,创建表只是数据库操作中最基本的一步而已,我们在一开始创建的表结构,随着需求的变更,到了后期是极有可能需要修改的。因此,升级表的操作对于任何一个项目也是至关重要的,那么今天我们就一起来学习一下,在Android传统开发当中升级表的方式,以及使用LitePal来进行升级表操作的用法。如果你还没有看过前一篇文章,建议先去参考一下 Android数据库高手秘籍(二)——创建表和LitePal的基本用法 。

LitePal的项目地址是:https://github.com/LitePalFramework/LitePal

传统的升级表方式

上一篇文章中我们借助MySQLiteHelper已经创建好了news这张表,这也是demo.db这个数据库的第一个版本。然而,现在需求发生了变更,我们的软件除了能看新闻之外,还应该允许用户评论,所以这时就需要对数据库进行升级,添加一个comment表。

该怎么做呢?添加一个comment表的建表语句,然后在onCreate()方法中去执行它?没错,这样的话,两张表就会同时创建了,代码如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class MySQLiteHelper extends SQLiteOpenHelper {  
  2.       
  3.     public static final String CREATE_NEWS = "create table news ("  
  4.             + "id integer primary key autoincrement, "  
  5.             + "title text, "  
  6.             + "content text, "  
  7.             + "publishdate integer,"  
  8.             + "commentcount integer)";  
  9.       
  10.     public static final String CREATE_COMMENT = "create table comment ("  
  11.             + "id integer primary key autoincrement, "  
  12.             + "content text)";  
  13.   
  14.     public MySQLiteHelper(Context context, String name, CursorFactory factory,  
  15.             int version) {  
  16.         super(context, name, factory, version);  
  17.     }  
  18.   
  19.     @Override  
  20.     public void onCreate(SQLiteDatabase db) {  
  21.         db.execSQL(CREATE_NEWS);  
  22.         db.execSQL(CREATE_COMMENT);  
  23.     }  
  24.   
  25.     @Override  
  26.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  27.     }  
  28.   
  29. }  
这对于第一次安装我们软件的用户来说是完全可以正常工作的,但是如果有的用户已经安装过上一版的软件,那么很遗憾,comment表是创建不出来的,因为之前数据库就已经创建过了,onCreate()方法是不会重新执行的。

对于这种情况我们就要用升级的方式来解决了,看到MySQLiteHelper构造方法中的第四个参数了吗,这个就是数据库版本号的标识,每当版本号增加的时候就会调用onUpgrade()方法,我们只需要在这里处理升级表的操作就行了。比较简单粗暴的方式是将数据库中现有的所有表都删除掉,然后重新创建,代码如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class MySQLiteHelper extends SQLiteOpenHelper {  
  2.       
  3.     ......  
  4.   
  5.     @Override  
  6.     public void onCreate(SQLiteDatabase db) {  
  7.         db.execSQL(CREATE_NEWS);  
  8.         db.execSQL(CREATE_COMMENT);  
  9.     }  
  10.   
  11.     @Override  
  12.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  13.         db.execSQL("drop table if exists news");  
  14.         onCreate(db);  
  15.     }  
  16.   
  17. }  
可以看到,当数据库升级的时候,我们先把news表删除掉,然后重新执行了一次onCreate()方法,这样就保证数据库中的表都是最新的了。

但是,如果news表中本来已经有数据了,使用这种方式升级的话,就会导致表中的数据全部丢失,所以这并不是一种值得推荐的升级方法。那么更好的升级方法是什么样的呢?这就稍微有些复杂了,需要在onUpgrade()方法中根据版本号加入具体的升级逻辑,我们来试试来吧。比如之前的数据库版本号是1,那么在onUpgrade()方法中就可以这样写:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class MySQLiteHelper extends SQLiteOpenHelper {  
  2.   
  3.     ......  
  4.   
  5.     @Override  
  6.     public void onCreate(SQLiteDatabase db) {  
  7.         db.execSQL(CREATE_NEWS);  
  8.         db.execSQL(CREATE_COMMENT);  
  9.     }  
  10.   
  11.     @Override  
  12.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  13.         switch (oldVersion) {  
  14.         case 1:  
  15.             db.execSQL(CREATE_COMMENT);  
  16.         default:  
  17.         }  
  18.     }  
  19.   
  20. }  
可以看到,这里在onUpgrade()方法中加入了一个switch判断,如果oldVersion等于1,就再创建一个comment表。现在只需要调用如下代码,表就可以得到创建或升级了:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. SQLiteOpenHelper dbHelper = new MySQLiteHelper(this"demo.db"null2);  
  2. SQLiteDatabase db = dbHelper.getWritableDatabase();  
这里我们将版本号加1,如果用户是从旧版本升级过来的,就会新增一个comment表,而如果用户是直接安装的新版本,就会在onCreate()方法中把两个表一起创建了。

OK,现在软件的第二版本也发布出去了,可是就在发布不久之后,突然发现comment表中少了一个字段,我们并没有记录评论发布的时间。没办法,只好在第三版中修复这个问题了,那我们该怎么样去添加这个字段呢?主要需要修改comment表的建表语句,以及onUpgrade()方法中的逻辑,代码如下所示:

[java]  view plain copy
  1. public class MySQLiteHelper extends SQLiteOpenHelper {  
  2.   
  3.     ......  
  4.   
  5.     public static final String CREATE_COMMENT = "create table comment ("  
  6.             + "id integer primary key autoincrement, "   
  7.             + "content text, "   
  8.             + "publishdate integer)";  
  9.   
  10.     ......  
  11.   
  12.     @Override  
  13.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  14.         switch (oldVersion) {  
  15.         case 1:  
  16.             db.execSQL(CREATE_COMMENT);  
  17.             break;  
  18.         case 2:  
  19.             db.execSQL("alter table comment add column publishdate integer");  
  20.             break;  
  21.         default:  
  22.         }  
  23.     }  
  24.   
  25. }  

可以看到,在建表语句当中我们新增了publishdate这一列,这样当执行onCreate()方法去创建表的时候,comment表中就会有这一列了。那么如果是从旧版本升级过来的呢?也没有问题,我们在onUpgrade()方法中已经把升级逻辑都处理好了,当oldVersion等于2的时候,会执行alter语句来添加publishdate这一列。现在调用以下代码来创建或升级数据库:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. SQLiteOpenHelper dbHelper = new MySQLiteHelper(this"demo.db"null3);  
  2. SQLiteDatabase db = dbHelper.getWritableDatabase();  
将数据库版本号设置成3,这样就可以保证数据库中的表又是最新的了。

现在我们已经学习了新增表和新增列这两种升级方式,那么如果是某张表中的某一列已经没有用了,我想把这一列删除掉该怎么写呢?很遗憾,SQLite并不支持删除列的功能,对于这情况,多数软件采取的作法是无视它,反正以后也用不到它了,留着也占不了什么空间,所以针对于这种需求,确实没什么简单的解决办法。

这大概就是传统开发当中升级数据库表的方式了,虽说能写出这样的代码表示你已经对数据库的升级操作理解的比较清楚了,但随着版本越来越多,onUpgrade()方法中的逻辑也会变得愈发复杂,稍微一不留神,也许就会产生错误。因此,如果能让代码自动控制升级逻辑,而不是由人工来管理,那就是再好不过了,那么下面我们就来学习一下怎样使用LitePal来进行升级表的操作。

使用LitePal升级表

通过上一篇文章的学习,我们已经知道LitePal是一款ORM模式的框架了,已经熟悉创建表流程的你,相信对于升级表也一定会轻车熟路的。那么为了模仿传统升级表方式中的需求,现在我们也需要创建一张comment表。第一步该怎么办呢?相信你也许早就已经猜到了,那当然是先创建一个Comment类了,如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.example.databasetest.model;  
  2.   
  3. public class Comment {  
  4.       
  5.     private int id;  
  6.       
  7.     private String content;  
  8.       
  9.     // 自动生成get、set方法   
  10.     ...  
  11. }  
OK,Comment类中有id和content这两个字段,也就意味着comment表中会有id和content这两列。

接着修改litepal.xml中的配置,在映射列表中新增Cooment类,并将版本号加1,如下所示:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <litepal>  
  3.     <dbname value="demo" ></dbname>  
  4.   
  5.     <version value="2" ></version>  
  6.   
  7.     <list>  
  8.         <mapping class="com.example.databasetest.model.News"></mapping>  
  9.         <mapping class="com.example.databasetest.model.Comment"></mapping>  
  10.     </list>  
  11. </litepal>  
没错,就是这么简单,仅仅两步,升级的操作就已经完成了,现在我们只需要操作一下数据库,comment表就会自动生成了,如下所示:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. SQLiteDatabase db = Connector.getDatabase();  
那么我们还是通过.table命令来查看一下结果,如下图所示:


OK,comment表已经出来了,那么再通过pragma命令来查看一下它的表结构吧:


没有问题,comment表中目前有id和content这两列,和Comment模型类中的字段是保持一致的。

那么现在又来了新的需求,需要在comment表中添加一个publishdate列,该怎么办呢?不用怀疑,跟着你的直觉走,相信你已经猜到应该在Comment类中添加这样一个字段了吧,如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class Comment {  
  2.       
  3.     private int id;  
  4.       
  5.     private String content;  
  6.       
  7.     private Date publishDate;  
  8.       
  9.     // 自动生成get、set方法   
  10.     ...  
  11. }  
然后呢?剩下的操作已经非常简单了,只需要在litepal.xml中对版本号加1就行了,如下所示:
[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <litepal>  
  2.     <dbname value="demo" ></dbname>  
  3.   
  4.     <version value="3" ></version>  
  5.     ...  
  6. </litepal>  
这样当我们下一次操作数据库的时候,publishdate列就应该会自动添加到comment表中。调用Connector.getDatabase()方法,然后重新查询comment表结构,如下所示:


可以看到,publishdate这一列确实已经成功添加到comment表中了。

通过这两种升级方式的对比,相信你已经充分体会到了使用LitePal进行升级表操作所带来的便利了吧。我们不需要去编写任何与升级相关的逻辑,也不需要关心程序是从哪个版本升级过来的,唯一要做的就是确定好最新的Model结构是什么样的,然后将litepal.xml中的版本号加1,所有的升级逻辑就都会自动完成了。LitePal确实将数据库表的升级操作变得极度简单,使很多程序员可以从维护数据库表升级的困扰中解脱出来。

然而,LitePal却明显做到了更好。前面我们提到过关于删除列的问题,最终的结论是无法解决,因为SQLite是不支持删除列的命令的。但是如果使用LitePal,这一问题就可以简单地解决掉,比如说publishdate这一列我们又不想要了,那么只需要在Comment类中把它删除掉,然后将版本号加1,下次操作数据库的时候这个列就会不见了。

那么有的朋友可能会问了,不是说SQLite不支持删除列的命令吗?那LitePal又是怎样做到的呢?其实LitePal并没有删除任何一列,它只是先将comment表重命名成一个临时表,然后根据最新的Comment类的结构生成一个新的comment表,再把临时表中除了publishdate之外的数据复制到新的表中,最后把临时表删掉。因此,看上去的效果好像是做到了删除列的功能。

这也是使用框架的好处,如果没有框架的帮助,我们显然不会为了删除一个列而大废周章地去写这么多的代码,而使用框架的话,具体的实现逻辑我们已经不用再关心,只需要控制好模型类的数据结构就可以了。

另外,如果你想删除某一张表的话,操作也很简单,在litepal.xml中的映射列表中将相应的类删除,表自然也就不存在了。其它的一些升级操作也都是类似的,相信你已经能举一反三,这里就不再赘述了。

好了,今天对LitePal的介绍就到这里吧,

Android数据库高手秘籍(四)——使用LitePal建立表关联

目前我们已经对LitePal的用法有了一定了解,学会了使用LitePal来创建表和升级表的方式,那么今天就让我们一起继续进阶,探究一下如何使用LitePal来建立表与表之间的关联关系。

关联关系的基础知识

喜欢把所有的代码都写在一个类里的程序员肯定是个新手。没错,任何一个像样的程序都不可能仅仅只有一个类的,同样地,任何一个像样的数据库也不可能仅仅只有一张表。我们都知道,在面向对象的编程语言中,多个类之间可以相互关联引用,共同完成某项功能。那么在数据库当中,多个表之间可以相互关联吗?当然可以!只不过表与表之间的关联关系要比对象之间的关联关系复杂一些,也更加难懂,但是作为数据库的基本功,还是应该了解清楚的,那么我们就先来学习一下数据库表关联的基础知识。

表与表之间的关联关系一共有三种类型,一对一、多对一、和多对多,下面我们分别对这三种类型展开进行讨论。

一对一

表示两个表中的数据必须是一一对应的关系。这种场景其实并不是很常见,我们还是通过例子来直观地体会一下,例子仍然是在之前文章的基础上展开的。

现在我们已经创建好了news这张表,里面主要记录了新闻的标题和内容,那么除了标题和内容之外,有些新闻还可能带有一些导语和摘要,我们把这两个字段放在一张introduction表中,作为新闻的简介。那么很显然,news表和introduction表就是一对一的关系了,因为一条新闻只能对应一个简介,一个简介也只能属于一条新闻。它们之间的对应关系大概如下图描述的一样:


可以看到,News1对应了Introduction2,News2对应了Introduction3,News3对应了Introduction1,但不管怎么样,它们都是一对一的关系。

那么这种一对一的关系,在编程语言中该怎么体现出来呢?相信熟悉面向对象设计的你,一定很轻松就能想出来吧,只需要在News类中持有一个Introduction类的引用,然后在Introduction类中也持有一个News类的引用,这样它们之间自然就是一对一的关系了。

没错,对象之间的一对一关系非常简单易懂,那么难点就在于,如何在数据库表中建立这样的一对一关系了。由于数据库并不像面向对象的语言一样支持相互引用,如果想让两张表之间建立一对一的关系,一般就只能通过外键的方式来实现了。因此,一对一关系的表结构就可以这样设计:


请注意,introduction表中有一个news_id列,这是一个外键列,里面应该存放一个具体的新闻id,这样一条introduction就能对应一条news,也就实现一对一的关系了,如下图所示:


由此我们就能够看出,id为1的introduction对应着id为2的news,id为2的introduction对应着id为3的news,id为3的introduction对应着id为1的news。需要注意的是,一对一的关系并没有强制要求外键必须加在哪一张表上,你可以在introduction表中加一个news_id作为外键,也可以在news表中加一个introduction_id作为外键,不管使用哪一种,都可以表示出它们是一对一的关联关系。

多对一

表示一张表中的数据可以对应另一张表中的多条数据。这种场景比起一对一关系就要常见太多了,在我们平时的开发工作中多对一关系真的是比比皆是。比如说现在我们的数据库中有一个news表,还有一个comment表,它们两个之间就是典型的多对一关系,一条新闻可以有很多条评论,但是一条评论只能是属于一条新闻的。它们的关系如下图所示:


而这种多对一的关系在编程语言中是非常容易体现出来的,比如Java中就有专门集合类,如List、Set等,使用它们的话就能轻松简单地在对象之间建立多对一的关系,我们稍后就会看到。那么,这里的难点仍然是在数据库表中如何建立这样的多对一关系。现在说难点其实已经不难了,因为前面我们已经学会了一对一关系的建立方法,而多对一也是类似的。没错,数据库表中多对一的关系仍然是通过外键来建立的,只不过一对一的时候外键加在哪一张表上都可以,但多对一的时候关键必须要加在多方的表中。因此,多对一关系的表结构就可以这样设计:


在comment表中有一个news_id列,这是一个外键列,里面应该存放一个具体的新闻id,并且允许多条comment都存放同一个新闻id,这样一条评论就只能对应一条新闻,但一条新闻却可以有多条评论,也就实现多对一的关系了,如下图所示:


由此我们就可以看出,id为1、2、3的三条评论是属于第一条新闻的,而id为4、5的两条评论是属于第二条新闻的。

多对多

表示两张关联表中的数据都可以对应另一张表中的多条数据。这种场景也不算是很常见,但比一对一关系要稍微更加常用一些。举个例子,我们都知道新闻网站是会将新闻进行种类划分的,这样用户就可以选择自己喜欢的那一类新闻进行浏览,比如说网易新闻中就会有头条、科技、娱乐、手机等等种类。每个种类下面当然都会有许多条新闻,而一条新闻也可能是属于多个种类的,比如iPhone6发布的新闻既可以属于手机种类,也可以属于科技种类,甚至还可以上头条。因此,新闻和种类之间就是一种多对多的关系,如下图所示:


可以看到,News1是属于Category1的,而News2和News3都是既属于Category1也属于Category2,如此复杂的关联关系该如何表示呢?在面向对象的编程语言中一切都是那么的简单,只需要在News类中使用集合类声明拥有多个Category,然后在Category类中也使用集合类声明拥有多个News就可以了,我们稍后就会看到。而难点仍然是留在了数据库上,两张表之间如何建立多对多的关联关系呢,还是用外键吗?肯定不行了,多对多的情况只能是借助中间表来完成了。也就是说,我们需要多建立一张表,这张表没什么其它作用,就是为了存放news表和category表之间的关联关系的,如下图所示:


注意这里我们建立一张名为category_news的中间表,中间表的命名并没有什么强制性的约束,但一个良好的命名规范可以让你一眼就明白这张表是用来做什么的。中间表里面只有两列,而且也只需要有两列,分别是news表的外键和category表的外键,在这里存放新闻和种类相应的id,就可以让它们之间建立关联关系了,如下图所示:


由此我们就可以看出,第一条新闻是属于第一个种类的,而第二和第三条新闻,则既属于第一个种类,也属于第二个种类。反过来也可以这样看,第一个种类下面有第一、第二、第三这三条新闻,而第二个种类下面只有第二、第三这两条新闻。不管怎么看,多对多的关系都是成立的。

好了,三种关联关系都讲完了,那我们来简单总结一下吧。虽说上面介绍了花了很大的篇幅讲解数据库的表关联知识,但其实最后的结论是非常简单的,大家可以当成口诀一样背下来。即一对一关联的实现方式是用外键,多对一关联的实现方式也是用外键,多对多关联的实现方式是用中间表。记下了这个口诀,在很多数据库设计的时候,你都可以发挥得更加游刃有余。

使用LitePal建立表关联

虽说口诀就是这个样子,但牵扯到表关联的时候毕竟增加了建表的难度,建表语句会更加复杂,你也需要格外地小心以防止出现什么错误。因此,使用LitePal来自动建立表关联又是一个非常不错的选择,我们不需要关心什么外键、中间表等实现的细节,只需要在对象中声明好它们相互之间的引用关系,LitePal就会自动在数据库表之间建立好相应的关联关系了,下面我们就来尝试一下吧。

首先确定一下一共涉及到了哪些实体类,News和Comment,这两个类我们在前两篇文章中就已经建好了,然后还需要有Introduction和Category这两个类,新建Introduction类,代码如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class Introduction {  
  2.       
  3.     private int id;  
  4.       
  5.     private String guide;  
  6.       
  7.     private String digest;  
  8.       
  9.     // 自动生成get、set方法  
  10. }  
接着新建Category类,代码如下所示:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class Category {  
  2.       
  3.     private int id;  
  4.       
  5.     private String name;  
  6.       
  7.     // 自动生成get、set方法  
  8. }  
现在四个类都已经建好了,但目前它们都还是各自独立的,互相之间没有任何联系,那么我们现在就开始用极为简单易懂的方式来给它们建立关联吧。首先,News和Introduction是一对一的关系,那就可以在News类中添加如下引用:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class News {  
  2.     ...  
  3.     private Introduction introduction;  
  4.       
  5.     // 自动生成get、set方法  
  6. }  
就是这么简单,在News类中可以得到一个对应的Introduction的实例,那么它们之间就是一对一关系了。

接着Comment和News是多对一的关系,因此News中应该包含多个Comment,而Comment中应该只有一个News,所以就可以这样写:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class News {  
  2.     ...  
  3.     private Introduction introduction;  
  4.       
  5.     private List<Comment> commentList = new ArrayList<Comment>();  
  6.       
  7.     // 自动生成get、set方法  
  8. }  
先使用一个泛型为Comment的List集合来表示News中包含多个Comment,然后修改Comment类的代码,如下所示:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class Comment {  
  2.     ...  
  3.     private News news;  
  4.       
  5.     // 自动生成get、set方法   
  6. }  
在Comment类中声明了一个News的实例,这样就清楚地表示出了News中可以包含多个Comment,而Comment中只能有一个News,也就是多对一的关系了。

最后News和Category是多对多的关系,相信聪明的你一定已经知道该怎么写了。News中可以包含多个Category,所以仍然应该使用List集合来表示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class News {  
  2.     ...  
  3.     private Introduction introduction;  
  4.       
  5.     private List<Comment> commentList = new ArrayList<Comment>();  
  6.       
  7.     private List<Category> categoryList = new ArrayList<Category>();  
  8.       
  9.     // 自动生成get、set方法  
  10. }  
而Category中也可以包含多个News,因此Category类也应该使用相同的写法,如下所示:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class Category {  
  2.     ...  
  3.     private List<News> newsList = new ArrayList<News>();  
  4.       
  5.     // 自动生成get、set方法  
  6. }  
这样就清楚地表达出它们之间是多对多的关联了。
关联关系都声明好了之后,我们只需要将所有的实体类都添加到映射列表当中,并将数据库版本号加1就可以了。修改litepal.xml的代码,如下所示:
[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <litepal>  
  3.     <dbname value="demo" ></dbname>  
  4.   
  5.     <version value="4" ></version>  
  6.   
  7.     <list>  
  8.         <mapping class="com.example.databasetest.model.News"></mapping>  
  9.         <mapping class="com.example.databasetest.model.Comment"></mapping>  
  10.         <mapping class="com.example.databasetest.model.Introduction"></mapping>  
  11.         <mapping class="com.example.databasetest.model.Category"></mapping>  
  12.     </list>  
  13. </litepal>  
基本上到这里就可以轻松地说结束了,现在只需要任意操作一下数据库,表之间的关联关系就将会自动建立,比如说调用一下Connector.getDatabase()方法。

下面我们来验证一下吧,输入.table命令查看一下当前数据库中的表,如下所示:


OK,news、comment、category、introduction这几张表全都有了,除此之外还有一张category_news中间表。那我们要来一一检查一下了,先查看一下introduction表的结构吧,如下所示:


可以看到,多了一个news_id列,说明introduction表和news表之间的一对一关系已经建立好了。

然后再检查一下comment表的结构,如下所示:


OK,comment表中也有一个news_id的列,那么comment表和news表之间的多对一关系也已经建立好了。

最后检查一下category_news这张中间表的结构,如下所示:


一共只有两列,一列是news_id,一列是category_id,分别对应着两张表的外键,这样news表和category表的多对多关系也建立好了。

借助LitePal的帮助,即使你并不熟悉数据库的表关联设计,只要你会面向对象编程,都可以轻松地将表与表之间的关联建立起来。创建表、升级表、表关联,这就是LitePal在数据库表管理方面给我们带来的巨大便利,相信大家都能体会到它的魅力所在了。那么到这里为止,我们就把使用LitePal进行表管理的知识全部学完了,



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值