Android SQLite OnUpgrade(): 问题
如果你编写过至少中等规模的安卓app,你很可能实现并利用了一个SQLite数据库。网上到处都是关于如何编写代码来实现的例子。尽管许多例子能够运行,但当需要升级并扩展app的数据库时,它们可能有些麻烦。
案例app
在这篇文章中,我们将想象我们有个app需要一张单独的SQLite表。这张表叫做Team。它将有下面的列:Id (int), Name (string), City (string), Mascot (string)。
标准 OnUpgrade() 教程
当我第一次开始制作安卓app,我找到了如下的教程(警告:这不是你想要拷贝的代码!):
public class SQLiteHelper extends SQLiteOpenHelper {
public SQLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
//database values
private static final String DATABASE_NAME = "demoApp.db";
private static final int DATABASE_VERSION = 1;
public static final String COLUMN_ID = "_id";
//team table
public static final String TABLE_TEAM = "team";
public static final String COLUMN_MASCOT = "mascot";
public static final String COLUMN_CITY = "city";
public static final String DATABASE_CREATE_TEAM = "create table "
+ TABLE_TEAM + "(" + COLUMN_ID + " integer primary key autoincrement, "
+ COLUMN_NAME + " string, "
+ COLUMN_MASCOT + " string, "
+ COLUMN_CITY + " string);";
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE_TEAM);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_TEAM);
onCreate(db);
}
}
当你处于开发早期阶段时,这份代码还算好用。你可以简单地通过改变创建语句和增加版本号来改变表。问题在于每次升级时,表都会被删除重建。这对于生产环境的app来说并不实际。你不会想要调用:
db.execSQL("DROP TABLE IF EXISTS " + TABLE_TEAM);
在每次发布一个新版本app时对每张表都这样处理。
稍好些的例子
一些意识到问题的开发者可能会谷歌搜索“android onupgrade add column.”不幸的是,截止本文编写为止,前6个结果中的5个都是不好的解决方案!你能在这儿,这儿,这儿,这儿和这儿看到这些方案。
它们都建议修改 OnUpdate() 函数来更好地改变旧版本中的字段参数。但是,它们都有一个问题。这里有一些改进的建议。看看你能不能找到每个例子的问题。
Bad Example 1
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
db.execSQL(ALTER_USER_TABLE_ADD_USER_SOCIETY);
db.execSQL(ALTER_USER_TABLE_ADD_USER_STREET1);
}
问题: 这些修改语句将在你每次更新app的时候执行!如果你从版本1升级到版本2,它们将运行并添加列。如果你从2升级到3(且不改变这段代码),这时会再次尝试增加字段并且爆出一个错误。你可能会想只在app改变的时候执行这段代码,但是这也很容易出错,并会使您遇到与示例3相同的错误。
Bad Example 2
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// If you need to add a column
if (newVersion > oldVersion) {
db.execSQL("ALTER TABLE foo ADD COLUMN new_column INTEGER DEFAULT 0");
}
}
问题:这是一个危险的例子,因为许多开发者只是简单地拷贝这段代码。问题在于每次升级时,if 条件都是 true,我们将产生和前一个例子一样的问题。
Bad Example 3
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
String upgradeQuery = "ALTER TABLE mytable ADD COLUMN mycolumn TEXT";
if (oldVersion == 1 && newVersion == 2)
db.execSQL(upgradeQuery);
}
问题:这个例子有些难找出问题。想象一下如果用户从版本1直接升级到版本3会发生什么。他们将完全错过这段升级代码!这将成为你app的重大问题。
正确方法
正如我们所见,我们不想在升级时删除数据库。我们也不想假定用户能每次都严格按顺序进行升级,我们确实希望在跑更新脚本时做一些版本检测。想象我们希望升级我们的app到版本2,使得表增加一个coach字段。而在版本3,我们还希望增加一个stadium字段。 我们可以使用下面的解决方案(你可以安全地使用这段代码。或者改动得更容易阅读和理解后再使用):
public class SQLiteHelper extends SQLiteOpenHelper {
public SQLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
//database values
private static final String DATABASE_NAME = "demoApp.db";
private static final int DATABASE_VERSION = 3;
public static final String COLUMN_ID = "_id";
//team table
public static final String TABLE_TEAM = "team";
public static final String COLUMN_MASCOT = "mascot";
public static final String COLUMN_CITY = "city";
public static final String COLUMN_COACH = "coach";
public static final String COLUMN_STADIUM = "stadium";
private static final String DATABASE_CREATE_TEAM = "create table "
+ TABLE_TEAM + "(" + COLUMN_ID + " integer primary key autoincrement, "
+ COLUMN_NAME + " string, "
+ COLUMN_MASCOT + " string, "
+ COLUMN_COACH + " string, "
+ COLUMN_STADIUM + " string, "
+ COLUMN_CITY + " string);";
private static final String DATABASE_ALTER_TEAM_1 = "ALTER TABLE "
+ TABLE_TEAM + " ADD COLUMN " + COLUMN_COACH + " string;";
private static final String DATABASE_ALTER_TEAM_2 = "ALTER TABLE "
+ TABLE_TEAM + " ADD COLUMN " + COLUMN_STADIUM + " string;";
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE_TEAM);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 2) {
db.execSQL(DATABASE_ALTER_TEAM_1);
}
if (oldVersion < 3) {
db.execSQL(DATABASE_ALTER_TEAM_2);
}
}
}
现在,app将执行其需要的更新语句。无论前一个版本是什么,也不管升级到的版本是什么,app将运行合适的语句使得app字段得到正确的更新。 还有一点需要注意的是,你还需要更改在onCreate中的创建语句。所以,如果你在更新中添加了一个字段,请将其添加到onCreate函数中的create语句(对于新用户),并将其添加到onUpgrade函数中的更新语句(对于老用户)。
你知道处理Android数据库迁移的更好的方法吗?将你的方法邮件给我们,如果我们觉得不错,将把它加入到这篇文章中。另外,如果你正在为你即将推出的app项目寻求帮助,可以查看我们的Android开发服务!