Android数据库高手秘籍(五)——LitePal的存储操作

997 阅读9分钟

经过前面几篇文章的学习,我们已经把 LitePal 的表管理模块的功能都很好地掌握了,相信大家都已经体会到了使用 LitePal 来创建表、升级表、以及建立表关联所带来的便利。

那么从本篇文章开始,我们将进入到一个新模块的学习旅程当中,使用 LitePal 来进行表的 CRUD 操作。还没有看过前一篇文章的朋友建议先去参考 Android 数据库高手秘籍 (四)——使用 LitePal 建立表关联

LitePal 提供的 CRUD 操作的 API 还是颇为丰富的,一篇文章肯定是介绍不全的,因此这里我们仍然是分几篇文章进行讲解,本篇主要是介绍存储方面的 API。

LitePal 的项目地址是:github.com/LitePalFram…

传统的存储数据方式

其实最传统的存储数据方式肯定是通过 SQL 语句拼接字符串来进行存储的,不过这种方式有点过于 “传统” 了,今天我们在这里就不讨论这种情况。实际上,Android 专门提供了一种用于存储数据的简便方法,使得我们不用编写 SQL 语句就可以执行存储操作。下面来看一下 SQLiteDatabase 中的 insert()方法:

public long insert(String table, String nullColumnHack, ContentValues values)

可以看到,insert 方法接收三个参数,第一个参数是表名,第二个参数通常都用不到,直接传 null,第三个参数则是一个封装了待存储数据的 ContentValues 对象。因此,比如说我们想往 news 表中插入一条新闻,就可以这样写:

SQLiteDatabase db = dbHelper.getWritableDatabase();

ContentValues values = new ContentValues();

values.put("title", "这是一条新闻标题");

values.put("content", "这是一条新闻内容");

values.put("publishdate", System.currentTimeMillis());

long id = db.insert("news", null, values);

其中,调用 ContentValues 的 put() 方法来添加待存储数据,put() 方法接收两个参数,第一个参数是数据库表中对应的列名,第二个参数就是要存储的值,最后调用一下 insert() 方法,这条新闻就会插入到 news 表当中了,并且该数据行对应的 id 会作为返回值进行返回。

用法很简单是吗?确实,比起直接使用 SQL 语句,SQLiteDatabase 中提供的 insert() 方法的确简单了很多。但 insert() 方法也并非是那么的完美,它还是有很多不方便的地方的,比如说没有考虑表关联的情况,我们需要手动对关联表的外键进行存储。再比如说,没有提供批量存储的功能,当我们有一个集合的数据需要存储时,需要通过循环来遍历这个集合,然后一次次地调用 insert() 方法来插入数据。

好了,那么关于传统存储数据的用法就简单介绍到这里,因为确实没什么的更多的用法了,并且它也不是我们今天的主角。接下来,就让我们看一看今天的惊喜,学习如何使用 LitePal 来进行数据库存储的操作。

使用 LitePal 存储数据

LitePal 中与存储相关的 API 其实并不多,但用法还是颇为丰富的,而且比起传统的 insert() 方法,使用 LitePal 来存储数据可以简单到让你惊叹的地步,那么今天我们就来完整地学习一下 LitePal 存储数据的所有用法。

在前面几篇文章当中,我们在项目里已经建好了 News、Comment、Introduction、Category 这几个实体类,通过这些实体类,LitePal 就可以把相应的表自动创建出来。现在来观察这几个实体类,我们发现这几个类都是没有继承结构的。没错,因为 LitePal 进行表管理操作时不需要这些实体类有任何的继承结构,当时为了简单起见就没有写。但是进行 CRUD 操作时就不行了,LitePal 要求所有的实体类都要继承自 DataSupport 这个类,因此这里我们就要把继承结构给加上才行。修改 News 类的代码,如下所示:

public class News extends DataSupport{

可以看到,这里只是让 News 类继承自了 DataSupport,其它什么都没有改变。另外几个 Comment、Introduction、Category 类也使用同样的改法,这里就不一一演示了。

继承了 DataSupport 类之后,这些实体类就拥有了进行 CRUD 操作的能力,那么比如想要存储一条数据到 news 表当中,就可以这样写:

news.setTitle("这是一条新闻标题");

news.setContent("这是一条新闻内容");

news.setPublishDate(new Date());

怎么样?是不是非常简单,不需要 SQLiteDatabase,不需要 ContentValues,不需要通过列名组装数据,甚至不需要指定表名,只需要 new 出一个 News 对象,然后把要存储的数据通过 setter 方法传入,最后调用一下 save() 方法就好了,而这个 save() 方法自然就是从 DataSupport 类中继承而来的了。

除此之外,save() 方法还是有返回值的,我们可以根据返回值来判断存储是否成功,比如说这样写:

	Toast.makeText(context, "存储成功", Toast.LENGTH_SHORT).show();

	Toast.makeText(context, "存储失败", Toast.LENGTH_SHORT).show();

可以看出,save() 方法返回的是一个布尔值,用于表示存储成功还是失败,但同时也说明这个方法是不会抛出异常的。有些朋友希望如果存储失败的话就抛出异常,而不是返回一个 false,那就可以使用 saveThrows() 方法来代替,如下所示:

news.setTitle("这是一条新闻标题");

news.setContent("这是一条新闻内容");

news.setPublishDate(new Date());

使用 saveThrows() 方法来存储数据,一旦存储失败就会抛出一个 DataSupportException 异常,我们可以通过对这个异常进行捕获来处理存储失败的情况。

那有些细心的朋友可能已经注意到,使用的 insert() 方法来存储数据时是有返回值的,返回的是插入行对应的 id。但 LitePal 中的 save() 方法返回的是布尔值,那么我们怎样才能拿到存储成功之后这条数据对应的 id 呢?对此,LitePal 使用了一种非常巧妙的做法,还记得我们在每个实体类中都定义了一个 id 字段吗?当调用 save() 方法或 saveThrows() 方法存储成功之后,LitePal 会自动将该条数据对应的 id 赋值到实体类的 id 字段上。让我们来做个试验吧,代码如下所示:

news.setTitle("这是一条新闻标题");

news.setContent("这是一条新闻内容");

news.setPublishDate(new Date());

Log.d("TAG", "news id is " + news.getId());

Log.d("TAG", "news id is " + news.getId());

在 save 之前打印一下 news 的 id,在 save 之后再打印一次,现在运行一下,打印结果如下所示:

OK,在 save 之前打印的 id 是 0,说明此时 id 这个字段还没有被赋值,在 save 之后打印的 id 是 1,说明此时 id 已经被赋值了。那么我们再到数据库表中再查看一下这条记录到底有没有存储成功吧,如下图所示:

可以看到,这条新闻确实已经存储成功了,并且对应的 id 正是 1,和我们前面打印的结果是一致的。

不过 LitePal 的存储功能显示不仅仅只有这些用法,事实上,LitePal 在存储数据的时候默默帮我们做了很多的事情,比如多个实体类之间有关联关系的话,我们不需要考虑在存储数据的时候怎么去建立数据与数据之间的关联,因为 LitePal 一切都帮我们做好了。

还是通过一个例子来看一下吧,Comment 和 News 之间是多对一的关系,一条 News 中是可以包含多条评论的,因此我们就可以这样写:

Comment comment1 = new Comment();

comment1.setContent("好评!");

comment1.setPublishDate(new Date());

Comment comment2 = new Comment();

comment2.setContent("赞一个");

comment2.setPublishDate(new Date());

news.getCommentList().add(comment1);

news.getCommentList().add(comment2);

news.setTitle("第二条新闻标题");

news.setContent("第二条新闻内容");

news.setPublishDate(new Date());

news.setCommentCount(news.getCommentList().size());

可以看到,这里先是存储了一条 comment1 数据,然后存储一条 comment2 数据,接着在存储 News 之前先把刚才的两个 Comment 对象添加到了 News 的 commentList 列表当中,这样就表示这两条 Comment 是属于这个 News 对象的,最后再把 News 存储到数据库中,这样它们之间的关联关系就会自动建立了。让我们查看数据库表检查一下吧,首先看一下 news 表,如下所示:

OK,第二条新闻已经成功存储到 news 表中了,这条新闻的 id 是 2。那么从哪里可以看出来关联关系呢?我们在上一篇文章中学过,多对一关联的时候,外键是存放在多方的,因此关联关系我们要到 comment 表中去查看,如下所示:

可以看到,两条评论都已经成功存储到 comment 表中了,并且这两条评论的 news_id 都是 2,说明它们是属于第二条新闻的。怎么样,仅仅是在存储数据之前建立好实体类之间的关系,再调用一下 save() 方法,那么数据之间的关联关系就会自动建立了,是不是非常简单?上面的代码只是多对一情况的一种用法,还有一对一和多对多的情况,其实用法都是差不多的,相信你已经能举一反三了。

另外,LitePal 对集合数据的存储还专门提供了一个方法,比如说我们有一个 News 集合,那么应该怎样去存储这个集合中的每条 News 呢?传统情况下可以这样写:

for (News news : newsList) {

通过一个循环来遍历出这个集合中的每一个 News 对象,然后逐个调用 save() 方法。这样的写法当然是可以的,但是效率会比较低,因为调用 save() 方法的时候除了会执行存储操作之外,还会去分析 News 类的关联关系,那么每次循环都去重新分析一遍关联关系显然是比较耗时的。因此,LitePal 提供了一个 saveAll() 方法,专门用于存储集合数据的,用法如下所示:

DataSupport.saveAll(newsList);

saveAll() 方法接收一个 Collection 集合参数,只要把待存储的集合数据传入即可。这个方法可以完成和上面一段代码完全一样的功能,但效率却会高得多,而且写法也更加简单。

好了,这样我们就把 LitePal 中提供的存储操作的用法全部都学完了,那么今天的文章就到这里,下一篇文章当中会开始讲解更新和删除操作的用法。感兴趣的朋友请继续阅读