作者:飞翔的猫咪 http://flyingcat2013.blog.51cto.com/7061638/1537074
数据库升级的意义
我们在开发Android应用的时候,不可避免地要使用数据库。而数据库的结构在第一版的时候定下来,之后发布功能更新,或增加业务逻辑,原来的数据库结构可能就不适用了。而如果数据库的结构与之前版本的结构不同,新版本的应用读取旧数据库肯定会出问题。解决办法只有两种:
1.让用户卸载老版本再安装新的程序;
2.软件自行更新数据库结构。
第一种办法很明显不具备可操作性,而且用户一旦卸载软件,数据就丢失了,这是不能容忍的事情。因此,作为开发者必须妥善处理数据库的升级问题。
当然了,有的同学会说,这问题没意义嘛。我们在设计软件的时候就把数据库设计得完备一点就好了,一开始就考虑周全,以后再也不用管升级的事情。这种方法理论上虽然可行,但实际操作起来是非常困难的,除非你在开发定制软件(例如某些和硬件结合的产品,其硬件发布之后就不再更新或很少更新,这样的软件确实没多大改动)。对于面向市场的应用来说,很可能在立项之初根本不会知道以后会增加哪些功能。这样,我们终究还是要面对这个问题。
保留数据的升级
现在以一个假想的程序数据库来谈如何保留原有的数据库进行升级。为直观起见,这里在每次软件版本安装之后导出了数据库文件,并使用SQLite Studio显示表结构和内容,过程从略。并且为了代码的简洁,省略了一些异常处理。我们假设数据库有这样三个版本:
v1:t_user(username, password);
v2:t_user(username, password), t_region(region, code);
v3:t_user(username, password), t_region(region, code, country);
可以看出,第一次升级增加了一张表,而第二次升级修改了表的定义。
创建和升级数据表的基本知识
我们在使用数据库之前,基本上会自定义一个类继承自SQLiteOpenHelper。该类的其中一个构造函数形式是这样的(另一个多出来一个DatabaseErrorHandler):
1
2
3
4
|
public
SQLiteOpenHelper(Context context, String name,
CursorFactory factory,
int
version) {
this
(context, name, factory, version,
null
);
}
|
这个构造函数里面的version参数即是我们设定的版本号。第一次使用数据库时传递的这个版本将被系统记录,并调用SQLiteOpenHelper#onCreate()方法进行建表操作。后续传入的版本如果比这个高,则会调用SQLiteOpenHelper#onUpgrade()方法进行升级。
增加一张表
很明显,增加新表(或者删除没有外键的表)的操作代价很小,只需要在onUpgrade()中写好建表操作和插入初始数据就好了。
1
2
3
4
5
6
7
|
public
void
onUpgrade(SQLiteDatabase db,
int
oldVersion,
int
newVersion) {
if
(oldVersion==
1
){
db.execSQL(
"CREATE TABLE t_region(_id integer primary key"
+
"autoincrement, region varchar, code varchar)"
);
//insert data...
}
}
|
从上面的图里可以看到,新版本的数据库中已经有t_region表了。
修改表定义
SQLite数库对ALTER TABLE命令支持非常有限,只能在表末尾添加列,不能修改列定义,不能删除已有的列。那么如果要修改表呢?我们可以采用临时表的办法。具体来说有四步:
将现有表重命名为临时表;
创建新表;
将临时表的数据导入新表(注意处理修改的列);
删除临时表。
以例子中的v2升级到v3为例:
1
2
3
4
5
6
7
8
9
10
11
|
public
void
onUpgrade(SQLiteDatabase db,
int
oldVersion,
int
newVersion) {
if
(oldVersion==
2
){
db.execSQL(
"ALTER TABLE t_region RENAME TO t_region_temp"
);
db.execSQL(
"CREATE TABLE t_region(_id integer primary key"
+
"autoincrement, region varchar, code varchar, "
+
"country varchar)"
);
db.execSQL(
"insert into t_region(_id, region, code, country) "
+
"select _id, region, code, \"CHINA\" from t_region_temp"
);
db.execSQL(
"DROP TABLE t_region_temp"
);
}
}
|
需要注意:
重命名表的SQL格式为ALTER TABLE
导入新数据的insert into select语句中不能出现values关键字;
记得删除临时表。
跨越版本的升级
处理好了单个版本的升级,还有一个更加棘手的问题:如果应用程序发布了多个版本,以致出现了三个以上数据库版本, 如何确保所有的用户升级应用后数据库都能用呢?有两种方式:
方式一:确定相邻版本的差别,从版本1开始依次迭代更新,先执行v1到v2,再v2到v3……
方式二:为每个版本确定与现在数据库的差别,为每个case撰写专门的升级代码。
方式一的优点是每次更新数据库的时候只需要在onUpgrade方法的末尾加一段从上个版本升级到新版本的代码,易于理解和维护,缺点是当版本变多之后,多次迭代升级可能需要花费不少时间,增加用户等待;
方式二的优点则是可以保证每个版本的用户都可以在消耗最少的时间升级到最新的数据库而无需做无用的数据多次转存,缺点是强迫开发者记忆所有版本数据库的完整结构,且每次升级时onUpgrade方法都必须全部重写。
以上简单分析了两种方案的优缺点,它们可以说在花费时间上是刚好相反的,至于如何取舍,可能还需要结合具体情况分析。
本文出自 “飞翔的猫咪” 博客,请务必保留此出处http://flyingcat2013.blog.51cto.com/7061638/1537074
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
本周着手开发数据同步的功能,但首先要解决的就是sqlite数据库升级的问题,关于数据库升级有蛮多方面涉及到,也许你是新增加了功能,所以新建了表,也许你为某些表增加了些字段,也许你是重构了数据模型与数据结构,不管如何升级,必须要满足用户正常升级的情况下原来的数据不会丢失。关于正确的数据库升级做法网上资料比较少,这次就来介绍下看到的国外一位大牛总结的数据库升级的正确做法。
大多数我们都是用android提供的SQLiteOpenHelper来创建和管理数据库,如下代码:
public class DbHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "mysample.db";
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_CREATE_SAMPLE_TABLE = "CREATE TABLE tblSample (_id integer primary key autoincrement, name varchar(32);";
public DbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase database) {
database.execSQL(DATABASE_CREATE_SAMPLE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Do nothing for now
}
}
上述代码每次执行的时候都会检查当前版本的数据库是否存在,如果不存在则会执行onCreate方法来创建新的数据库,然后会把数据库的名字和版本号存储起来;如果已经存在,则会比较当前版本和DATABASE_VERSION的大小,然后就会去执行onUpgrade方法。
问题是,最好的处理升级的方法是什么,这里认为最好的方法是循环处理每一个版本的数据库变化,看示例:
假设下一版本想为tblSample表新增一个“address”的字段,新的创建语句应该像这样:
CREATE TABLE tblSample
(
_id integer primary key autoincrement,
name varchar(32),
address varchar(128)
);
那么看下新的代码会是什么样的:
public class DbHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "mysample.db";
private static final int DATABASE_VERSION = 2;
private static final String DATABASE_CREATE_SAMPLE_TABLE = "CREATE TABLE tblSample (_id integer primary key autoincrement, name varchar(32), address varchar(128);";
public DbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase database) {
database.execSQL(DATABASE_CREATE_SAMPLE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
for (int i = oldVersion; i < newVersion; i++) {
switch (i) {
case 1:
db.execSQL("ALTER TABLE tblSample ADD address varchar(128)");
break;
}
}
}
}
代码逻辑很简单,就是一个for循环加上switch…case…语句,然后上述代码却能处理所有的数据库升级,不管你是从版本1升级到版本9也好,还是从版本4升级到版本5也好,都可以从容的解决,确切的说,它将能解决从之前的所有版本升级到当前最新的版本。
须要说明的是,如果有些莫名其妙的用户从高版本升级到低版本(确切的说是降级),例如从版本3不小心降级到版本1了,这种情况下如果只是有了上述代码则就会抛出异常,造成系统崩溃。android中数据库降级则会执行onDowngrade方法,为防止有这种情况发生,同样须要重新这个方法防止程序的异常。
转自:http://stormzhang.github.io/android/sqlite/2013/08/11/android-sqlite-database-upgrade/