版权声明:本文为博主原创文章,未经博主允许不得转载。
前段时间写Android用到SQLIteOpenHelper
时,踩了一些小坑,仔细思考了一下,踩坑的根源在于,我没有正确的理解SQLiteOpenHelper
这个类。那么,我们一起来看看SQLiteOpenHelper
究竟是个什么东东,以及如何正确地使用SQLiteOpenHelper
,希望可以帮助博友们理解,避免踩到了类似的坑。
本文的将首先介绍下SQLiteOpenHelper
的作用,然后引申出一些在使用SQLiteOpenHelper
时需要注意的事项,最后,通过一个小demo演示下SQLiteOpenHelper
操作数据库。
SQLiteOpenHelper是什么
望文生义,从名字上看来,至少可以得到三个方面的信息。第一,和SQLite数据库有关。第二,和打开一个数据库有关。第三,这是一个帮助类。
当然,这些都是从名字上联想到的,难免有主观臆断之嫌,哈哈。我们来看看官方的说法:
A helper class to manage database creation and version management.
所以,官方把SQLiteOpenHelper的作用解释为:一个数据库创建和版本管理的帮助类。
引起我注意的是,类名起的和解释不是很切合。
既然是管理的帮助类,并且是管理数据库的创建和版本,那么为什么类名包含"Open"这个单词呢?为什么不叫SQLiteManageHelper
,或者更准确的,SQLiteCreationAndUpgradeHelper
呢?且听下文分解。
我们知道,我们可以通过sql来操纵数据库,做增删改查。但是呢,如果是我们从头开始写一遍数据库逻辑的话,其实不只sql操作。一个简单的流程是这样的:打开数据库->sql操作数据库->关闭数据库。而打开数据库,又牵涉到了不少细节。比如:
- 这个数据库是否存在?如果不存在,就要考虑先创建数据库,创建各种数据表。
- 这个数据库是否需要升级,比如目前的数据库结构已经不能满足我们的需求了,我们需要更改数据库的结构,这个时候就要先升级数据库,更新数据库的结构。
- 这个数据库是否之前已经打开?一般来说,我们操作数据库结束之后,需要关闭数据库,以节约资源。但是如果我们之前打开的数据库还要继续使用,这时候再执行打开操作时,直接返回这个数据库就行了。
这些细节实现起来,还是挺繁琐的,然而这些逻辑都是套路,一招鲜吃遍天,不需要每次都从头写一遍。于是,是时候SQLiteOpenHelper出场了。
SQLiteOpenHelper把打开数据库的一系列需要考虑到的细节逻辑都封装了起来,这也是为什么包含了Open这个单词的原因了。因为SQLiteOpenHelper的主要工作就是把打开数据库的逻辑帮我们实现了。
当然,打开数据库中涉及到的一些业务方面的逻辑,这是需要我们自己去实现的,SQLiteOpenHelper把这些用相应的接口分离了出来。
-
onCreate
: 创建数据库时会调用,我们需要在里面创建我们的数据表。onCreate只会调用一次,如果数据库已经存在,那么打开数据库是不会回调onCreate的。 -
onUpgrade
: 升级数据库时会调用,我们可以在这里面做升级操作。关于升级,还有一点需要注意的,后面会提到。 -
onDowngrade
: 降级数据库时会调用,既然存在升级的场景,也可能存在降级的场景,比如我们可能觉得新版的需求没有旧版的好,想要恢复以前的需求,这时候就需要降级处理了。 -
onOpen
: 打开数据库时会调用到。和onCreate有所区别的是,onCreate只在创建数据库时会调用,而onOpen在每次打开数据库时都会调用。 -
onConfigure
:配置数据库连接。比如设置外键约束等。
SQLiteOpenHelper的几个误区
引入正题,我们来看看几个使用SQLiteOpenHelper可能踩到的坑或者误区。
-
误区一:创建SQLiteOpenHelper对象的时候,会创建数据库或者连接数据库
不知道用过SQLiteOpenHelper的朋友们有没有和我一样,曾经犯过这样的错误,以为创建SQLiteOpenHelper对象的时候,就会创建相应的数据库,或者连接已经存在的数据库。为什么会产生这样的误解呢?仔细想了想,根源出在构造函数上。
publicSQLiteOpenHelper(Context context, String name, CursorFactory factory,intversion,DatabaseErrorHandler errorHandler)
dbName是数据库的名字,version是数据库的版本。既然构造SQLiteOpenHelper的时候,又是传dbName,又是传version的,我就想当然的以为这时候会创建数据库并且连接上数据库了。然后打开源码发现,什么都没发生[看不下去]
publicSQLiteOpenHelper(Context context, String name, CursorFactory factory,intversion,DatabaseErrorHandler errorHandler) {
if(version <1)thrownewIllegalArgumentException("Version must be >= 1, was "+ version);
mContext = context;
mName = name;
mFactory = factory;
mNewVersion = version;
mErrorHandler = errorHandler;
}
SQLiteOpenHelper的构造函数里只是简单的记录下传过来的参数。
真正创建数据库的时机是,显示调用打开数据库的api才会执行。也就是getReadableDatabase()和getWriteableDatabase()。这两个方法都是调用了
privateSQLiteDatabase getDatabaseLocked(booleanwritable)
而创建数据库,配置数据库,升级数据库,打开数据库这些逻辑都是在这个方法里面实现的。
-
误区二:在onCreate, onOpen等回调里面调用getReadableDatabase或getWriteableDatabase
因为前面解释得很清楚了,getReadableDatabase或getWriteableDatabase是触发(创建数据库、打开数据库、升级数据库、降级数据库、配置数据库)的入口,如果在这些回调里面,又触发了getReadableDatabase或getWriteableDatabase的话,你没看错,这变成递归调用了。
所以,不能再onCreate, onOpen, onConfigure, onUpgrade, onDowngrade回调里面调用打开数据库的方法。 -
误区三:升级数据库时,只需要考虑从最近的一个版本升级到最新的版本
这种做法是错误的。举个栗子。
假设我们的app一共发了三个版本A,B,C,并且版本A的数据库version是1,版本B的数据库version升到了2,版本C的数据库version升到了3。
版本B里面的数据库升级逻辑是
@Override
publicvoidonUpgrade(SQLiteDatabase sqLiteDatabase,intoldVersion,intnewVersion) {
// 在student表里面加了一个hobby字段
}
版本C里面的数据库升级逻辑是
@Override
publicvoidonUpgrade(SQLiteDatabase sqLiteDatabase,intoldVersion,intnewVersion) {
// 在student表里面加了一个hobby字段
}
这会导致一个很严重的问题。假如用户从app的版本A升级到版本B再升级到版本C,一切沿着我们的设想,没有问题。但是如果用户在收到版本B的更新时,拒绝了更新请求;而收到版本C的更新时,同意了更新。那么问题来了,student表里面增加了hobby字段,但少了age字段!因为用户拒绝了版本B的更新,意味着没有执行版本B里面的数据库升级逻辑。
正确的做法是这样的,在版本C里面,判断老版本,如果是1,说明是从版本A升级过来的,那么就应该添加age, hobby字段;如果是2,说明是从版本B升级过来的,那么只需要添加hobby字段,因为版本B里面已经有age字段,不需要添加了。
@Override
publicvoidonUpgrade(SQLiteDatabase sqLiteDatabase,intoldVersion,intnewVersion) {
if(oldVersion ==1) {
// 在student表里面加了age, hobby字段
}elseif(oldVersion ==2) {
// 在student表里面加了hobby字段
}
}
长话短说,升级数据库,一定要考虑以前的所有版本,对每个版本都进行适配。
当然,有升级的场景,就有降级的场景。逻辑是类似的,降级的时候,要考虑所有的高版本,对每个高本版都进行适配。
SQLiteOpenHelper的小Demo
做了一个小demo,实现了几个小功能:
- 创建数据库student_db,创建数据表student
- 添加一行数据到student表
- 修改一行数据
- 删除一行数据
- 按名字查询student表
- 升级数据库,给student表添加一个age字段,默认值是20岁
下面是demo的下载链接,欢迎下载:
Sqlite Demo
总结
- SQLiteOpenHelper名字里包含Open,就是想告诉我们,SQLiteOpenHelper的主要工作就是把打开数据库的逻辑帮我们实现了。
- 创建SQLiteOpenHelper对象的时候,不会真正创建数据库或者连接数据库。调用getReadableDatabase或getWriteableDatabase才会触发该操作。
- 升级数据库,一定要考虑以前的所有版本,对每个版本都进行适配。降级的时候,要考虑所有的高版本,对每个高本版都进行适配。
- 在onCreate, onOpen等回调不能调用getReadableDatabase或getWriteableDatabase,会产生递归调用。
感谢您的耐心阅读,以上如果有错误的地方或者理解有失偏颇,请留言指正,谢谢~~