SQLite 数据库存储
Posted 码了么
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SQLite 数据库存储相关的知识,希望对你有一定的参考价值。
SQLite 是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百 K 的内存就足够了,因而特别适合在移动设备上使用。SQLite不仅支持标准的 SQL 语法,还遵循了数据库的 ACID 事务。而 SQLite 又比一般的数据库要简单得多,它甚至不用设置用户名和密码就可以使用。android 正是把这个功能极为强大的数据库嵌入到了系统当中,使得本地持久化的功能有了一次质的飞跃。
SQLiteOpenHelper 是一个抽象类,这意味着如果我们想要使用它的话,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper 中有两个抽象方法,分别是 onCreate()和 onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
SQLiteOpenHelper 中还有两个非常重要的实例方法,getReadableDatabase()和 getWritableDatabase()。这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而 getWritableDatabase()方法则将出现异常。
SQLiteOpenHelper 中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。这个构造方法中接收四个参数,第一个参数是 Context,必须要有它才能对数据库进行操作。第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。第三个参数允许我们在查询数据的时候返回一个自定义的 Cursor,一般都是传入 null。第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。构建出SQLiteOpenHelper 的实例之后,再调用它的 getReadableDatabase()或 getWritableDatabase()方法就能够创建数据库了,数据库文件会存放在/data/data/<package name>/databases/目录下。此时,重写的 onCreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。
下面我们通过例子的方式来更加直观地体会 SQLiteOpenHelper 的用法,首先新建一个 DatabaseTest 项目。这里我们希望创建一个名为BookStore.db的数据库,然后在这个数据库中新建一张Book表,表中有 id(主键)、作者、价格、页数和书名等列。创建数据库表需要用建表语句,Book 表的建表语句如下所示
SQLite 不像其他的数据库拥有众多繁杂的数据类型,它的数据类型很简单,integer 表示整型,real 表示浮点型,text 表示文本类型,blob 表示二进制类型。另外,上述建表语句中我们还使用了 primary key 将 id 列设为主键,并用 autoincrement关键字表示 id 列是自增长的。
然后需要在代码中去执行这条 SQL语句,才能完成创建表的操作。新建 MyDatabaseHelper类继承自 SQLiteOpenHelper,代码如下所示:
可以看到,我们把建表语句定义成了一个字符串常量,然后在 onCreate()方法中又调用了 SQLiteDatabase的 execSQL()方法去执行这条建表语句,并弹出一个 Toast 提示创建成功,这样就可以保证在数据库创建完成的同时还能成功创建 Book 表。
现在修改 activity_main.xml 中的代码,加入一个按钮用于创建数据库,如下所示:
最后修改 MainActivity 中的代码,如下所示:
再次点击 Create database按钮时,会发现此时已经存在 BookStore.db 数据库了,因此不会再创建一次。
现在就可以运行一下代码了,在程序主界面点击 Createdatabase按钮,此时 BookStore.db 数据库和 Book 表应该都已经创建成功了,因为当你再次点击 Create database按钮时不会再有 Toast 弹出。
现在我们使用 adb shell来对数据库和表的创建情况进行检查。adb 是 Android SDK 中自带的一个调试工具,使用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。它存放在 sdk 的 platform-tools 目录下。在该路径下打开命令行界面,输入 adb shell,就会进入到设备的控制台,如图所示:
然后使用 cd 命令进行到/data/data/com.example.administrator.databasetest/databases/目录下(权限不够的话需要先输入命令adb root),并使用 ls命令查看到该目录里的文件,如图所示:
这个目录下出现了两个数据库文件,一个正是我们创建的 BookStore.db,而另一个 BookStore.db-journal 则是为了让数据库能够支持事务而产生的临时日志文件,通常情况下这个文件的大小都是 0 字节。
接下来我们就要借助 sqlite 命令来打开数据库了,只需要键入 sqlite3,后面加上数据库名即可,如图所示:
这时就已经打开了 BookStore.db 数据库,现在就可以对这个数据库中的表进行管理了。首先来看一下目前数据库中有哪些表,键入.table命令,如图所示:
可以看到,此时数据库中有两张表,android_metadata 表是每个数据库中都会自动生成的,不用管它,而另外一张 Book 表就是我们在 MyDatabaseHelper中创建的了。这里还可以通过.schema 命令来查看它们的建表语句,如图所示:
由此证明,BookStore.db 数据库和 Book 表确实已经是创建成功了。之后键入 .exit 或 .quit命令可以退出数据库的编辑,再键入 exit 命令就可以退出设备控制台了。
onUpgrade()方法是用于对数据库进行升级的,它在整个数据库的管理工作当中起着非常重要的作用。
目前 DatabaseTest 项目中已经有一张 Book 表用于存放书的各种详细数据,如果我们想再添加一张 Category 表用于记录书籍的分类该怎么做呢?
比如 Category 表中有 id(主键)、分类名和分类代码这几个列,那么建表语句就可以写成:
接下来我们将这条建表语句添加到 MyDatabaseHelper 中,代码如下所示:
可以看到,我们在 onUpgrade()方法中执行了两条 DROP 语句,如果发现数据库中已经存在 Book 表或 Category 表了,就将这两张表删除掉,然后再调用 onCreate()方法去重新创建。
接下来的问题就是如何让 onUpgrade()方法能够执行了,SQLiteOpenHelper 的构造方法里接收的第四个参数表示当前数据库的版本号,之前我们传入的是 1,现在只要传入一个比 1 大的数,就可以让 onUpgrade()方法得到执行了。修改 MainActivity 中的代码,如下所示:
这里将数据库版本号指定为 2,表示我们对数据库进行升级了。现在重新运行程序,并点击 Create database按钮,这时就会再次弹出创建成功的提示。为了验证一下 Category 表是不是已经创建成功了,我们在 adb shell 中打开 BookStore.db 数据库,然后键入.table 命令,接着键入.schema命令查看一下建表语句,结果如图所示:
由此可以看出,Category 表已经创建成功了,同时也说明我们的升级功能的确起到了作用。
其实我们可以对数据进行的操作也就无非四种,即 CRUD。其中 C 代表添加
(Create),R 代表查询(Retrieve),U 代表更新(Update),D代表删除(Delete)。每一种操作又各自对应了一种 SQL 命令,添加数据时使用 insert,查询数据时使用 select,更新数据时使用 update,删除数据时使用 delete。Android
提供了一系列的辅助性方法,使得在 Android 中即使不去编写 SQL 语句,也能轻松完成所有的 CRUD 操作。
前面我们已经知道,调用 SQLiteOpenHelper的 getReadableDatabase()或 getWritableDatabase()方法是可以用于创建和升级数据库的,不仅如此,这两个方法还都会返回一个SQLiteDatabase对象,借助这个对象就可以对数据进行 CRUD 操作了。
SQLiteDatabase中提供了一个 insert()方法,这个方法就是专门用于添加数据的。它接收三个参数,第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值 NULL,一般我们用不到这个功能,直接传入 null 即可。第三个参数是一个 ContentValues 对象,它提供了一系列的 put()方法重载,用于向 ContentValues 中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。
修改 activity_main.xml 中的代码,在布局文件中新增一个按钮,如下所示:
接着修改 MainActivity 中的代码,为按钮添加逻辑,如下所示:
在添加数据按钮的点击事件里面,我们先获取到了 SQLiteDatabase 对象,然后使用 ContentValues来对要添加的数据进行组装。这里只对 Book表里其中四列的数据进行了组装,id 那一列没并没给它赋值,这是因为在前面创建表的时候我们就将 id 列设置为自增长了,它的值会在入库的时候自动生成,所以不需要手动给它赋
值了。接下来调用了 insert()方法将数据添加到表当中,注意这里我们实际上添加了两条数据,上述代码中使用 ContentValues分别组装了两次不同的内容,并调用了两次 insert()方法。
现在重新运行一下程序,点击一下 Add data 按钮,此时两条数据应该都已经添加成功了,不过为了证实一下,我们还是打开 BookStore.db 数据库瞧一瞧。输入 SQL 查询语句 select * from book;,结果如图所示:
由此可以看出,我们刚刚组装的两条数据,都已经准确无误地添加到 Book 表中了。
SQLiteDatabase 中也是提供了一个非常好用的 update()方法用于对数据进行更新,这个方法接收四个参数,第一个参数和 insert()方法一样,也是表名,在这里指定去更新哪张表里的数据。第二个参数是 ContentValues 对象,要把更新数据在这里组装进去。第三、第四个参数用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。
接下来我们仍然是在 DatabaseTest项目的基础上修改,看一下更新数据的具体用法。比如说刚才添加到数据库里的第一本书,由于过了畅销季,卖得不是很火了,现在需要通过降低价格的方式来吸引更多的顾客。首先修改 activity_main.xml 中的
代码,添加一个用于更新数据的按钮,如下所示:
然后修改 MainActivity 中的代码,为按钮添加逻辑,如下所示:
这里在更新数据按钮的点击事件里面构建了一个 ContentValues 对象,并且只给它指定了一组数据,说明我们只是想把价格这一列的数据更新成 10.99。然后调用了 SQLiteDatabase的 update()方法去执行具体的更新操作,可以看到,这里使用了第三、第四个参数来指定具体更新哪几行。第三个参数对应的是 SQL 语句的 where 部分,表示去更新所有 name 等于?的行,而?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。因此上述代码想表达的意图就是,将名字是 The Da Vinci Code的这本书的价格改成 10.99。
现在重新运行一下程序,点击一下 Update data 按钮后,再次输入查询语句查看表中的数据情况,结果如图所示:
可以看到,The Da Vinci Code 这本书的价格已经被成功改为 10.99 了。
SQLiteDatabase 中提供了一个 delete()方法专门用于删除数据,这个方法接收三个参数,第一个参数仍然是表名,这个已经没什么好说的了,第二、第三个参数又是用于去约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。
我们继续修改 activity_main.xml中的代码,在布局文件中添加一个按钮用于删除数据,然后修改 MainActivity 中的代码为该按钮添加逻辑,如下图:
可以看到,我们在删除按钮的点击事件里指明去删除 Book 表中的数据,并且通过第二、第三个参数来指定仅删除那些页数超过 500 页的书籍。你可以先查看一下当前 Book 表里的数据,其中 The Lost Symbol 这本书的页数超过了 500 页,也就是说当我们点击删除按钮时,这条记录应该会被删除掉。
现在重新运行一下程序,点击一下 Delete data 按钮后,再次输入查询语句查看表中的数据情况,结果如图所示:
这样就可以明显地看出,The Lost Symbol 这本书的数据已经被删除了。
SQLiteDatabase中还提供了一个 query()方法用于对数据进行查询。
这个方法的参数非常复杂,最短的一个方法重载也需要传入七个参数。第一个参数当然还是表名,表示我们希望从哪张表中查询数据。第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。第三、第四个参数用于去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据。第五个参数用于指定需要去 group by 的列,不指定则表示不对查询结果进行 group by 操作。第六个参数用于对 group by 之后的数据进行进一步的过滤,不指定则表示不进行过滤。第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。更多详细的内容可以参考下表。其他几个 query()方法的重载其实也大同小异,你可以自己去研究一下,这里就不再进行介绍了。
我们不必为每条查询语句都指定上所有的参数,多数情况下只需要传入少数几个参数就可以完成查询操作了。调用 query()方法后会返回一个 Cursor 对象,查询到的所有数据都将从这个对象中取出。
下面还是让我们通过例子的方式来体验一下查询数据的具体用法,修改 activity_main.xml中的代码,添加一个按钮用于查询数据,然后修改 MainActivity 中的
代码,为该按钮添加逻辑,如下所示:
可以看到,我们首先在查询按钮的点击事件里面调用了 SQLiteDatabase 的 query()方法去查询数据。这里的 query()方法非常简单,只是使用了第一个参数指明去查询 Book 表,后面的参数全部为 null。这就表示希望查询这张表中的所有数据,虽然这张表中目前只剩下一条数据了。查询完之后就得到了一个 Cursor 对象,接着我们调用它的 moveToFirst()方法将数
据的指针移动到第一行的位置,然后进入了一个循环当中,去遍历查询到的每一行数据。在这个循环中可以通过 Cursor 的 getColumnIndex()方法获取到某一列在表中对应的位置索引,然后将这个索引传入到相应的取值方法中,就可以得到从数据库中读取到的数据了。接着我们使用 Log 的方式将取出的数据打印出来,借此来检查一下读取工作有没有成功完成。最后别忘了调用 close()方法来关闭 Cursor。
现在再次重新运行程序,点击一下 Query data 按钮后,查看 LogCat 的打印内容,结果如图所示:
可以看到,这里已经将 Book 表中唯一的一条数据成功地读取出来了。
虽然 Android 已经给我们提供了很多非常方便的 API 用于操作数据库,不过总会有一些人不习惯去使用这些辅助性的方法,而是更加青睐于直接使用 SQL 来操作数据库。
Android 同样提供了一系列的方法,使得可以直接通过 SQL 来操作数据库。
下面简略演示一下如何直接使用 SQL 来完成前面几小节中学过的 CRUD 操作。
添加数据的方法如下:
更新数据的方法如下:
删除数据的方法如下:
查询数据的方法如下:
可以看到,除了查询数据的时候调用的是 SQLiteDatabase 的 rawQuery()方法,其他的操作都是调用的 execSQL()方法。以上的几种方式,执行结果会和前面几小节中我们学习的 CRUD 操作的结果完全相同。
我们之前学习的升级数据库的方式是非常粗暴的,为了保证数据库中的表是最新的,我们只是简单地在 onUpgrade()方法中删除掉了当前所有的表,然后强制重新执行了一遍 onCreate()方法,用户更新了这个版本之后会发现以前程序中存储的本地数据全部丢失了。其实只需要进行一些合理的控制,就可以保证在升级数据库的时候数据并不会丢失了。
下面我们就来学习一下如何实现这样的功能,你已经知道每一个数据库版本都会对应一个版本号,当指定的数据库版本号大于当前数据库版本号的时候,就会进入到 onUpgrade()方法中去执行更新操作。这里需要为每一个版本号赋予它各自改变的内容,然后在 onUpgrade()方法中对当前数据库的版本号进行判断,再执行相应的改变就可以了。
我们模拟一个数据库升级的案例,还是由 MyDatabaseHelper 类来对数据库进行管理:
我们知道,我们在 onCreate()方法里新增了一条建表语句,然后又在 onUpgrade()方法中添加了一个 switch 判断,如果用户当前数据库的版本号是 1,就只会创建一张 Category 表。这样当用户是直接安装的第二版的程序时,就会将两张表一起创建。而当用户是使用第二版的程序覆盖安装第一版的程序时,就会进入到升级数据库的操作中,此时由于 Book 表已经存在了,因此只需要创建一张 Category 表即可。
另外,我们在 Book 表的建表语句中添加了一个 category_id 列,这样当用户直接安装第三版的程序时,这个新增的列就已经自动添加成功了。然而,如果用户之前已经安装了某一版本的程序,现在需要覆盖安装,就会进入到升级数据库的操作中。在 onUpgrade()方法里,我们添加了一个新的 case,如果当前数据库的版本号是 2,就会执行 alter 命令来为 Book 表新增一个 category_id 列。
这里请注意一个非常重要的细节,switch 中每一个 case的最后都是没有使用 break 的,这是为了保证在跨版本升级的时候,每一次的数据库修改都能被全部执行到。比如用户当前是从第二版程序升级到第三版程序的,那么 case 2 中的逻辑就会执行。而如果用户是直接从第一版程序升级到第三版程序的,那么 case1 和 case2 中的逻辑都会执行。使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据也完全不会丢失了。
关注公众号了解更多内容
以上是关于SQLite 数据库存储的主要内容,如果未能解决你的问题,请参考以下文章
SQLite 和 Firebase 数据库之间的同步,当用户离线数据存储在 sqlite 和在线数据存储在 firebase 时