做Android APK应用的时候要经常和数据库打交道。在伴随着APK升级的过程中数据库可能也会有变化也要升级,我们要保证用户之前数据不流失。所以SQLite提供了onUpgrade一系列的方法来满足需求。关于数据库升级我们应该知道。

1. 简单的介绍

  1. 当APK运行的时候如果数据库文件不存在,SQLiteOpenHelper在自动创建数据库后会调用onCreate()方法,在该方法中一般需要创建表、视图等组件。在创建前数据库一般是空的,因此不需要先删除数据库中相关的组件。调用的时机是首次安装APK运行的时候或者清除了APK的数据之后。
  2. 如果数据库文件存在,并且当前版本号高于上次创建或升级的版本号,SQLiteOpenHelper会调用onUpdate()方法,调用该方法后会更新数据库的版本号。在onUpdate()方法中做一些相关的变化的操作,在调用onUpdate()方法前,数据库是存在的,里面还有上一个版本的数据。调用时机是应用更新做覆盖安装后。

可能有人会有疑问对于数据库有升级肯定也有降级的吧。是的有的。

对于升级比如我们现在的数据库版本是3,我们肯定知道1,2两个版本的情况。数据库做了哪些具体的变化我们是明确的。所以我们在onUpdate()函数中做一些数据库变化的相应处理。这个情况我们是能控制住的。

对于数据库降级比如市面上有三个数据库的版本1,2,3 现在你想从3版本更回到2版本。因为2数据库的版本的软件是早先就发布出去的。在2数据库版本的时候我们根本就不知道3版本数据库是什么情况。根本就预知不到数据库的变化。这样这种情况没办法的办法就是重写onDowngrade()函数把当前版本(2版本)中用得到的数据库表drop(删掉)在调用onCreate() 重建一次。注意onDowngrade()重写的时候不能调用super.onDowngrade()。这个里面会抛异常。

所以可以得出一个结论。如果数据库文件不存在,只有onCreate()被调用。如果数据库文件存在,会调用onUpdate()方法升级数据库,并更新版本号。

升级过程中可能出现以下几种情况。
一. 表的创建和删除。

二. 是对某个表进行了字段的添加(注:一般加在末尾)。

三. 稍微复杂的操作,比如在修改的同时,需要进行数据的转移,那么可以采取在一个事务中执行如下语句来实现修改表的需求。
  1. 将表名改为临时表(方便导入数据的时候用)
  2. 创建新表(表的名字也是第一步中改名之前的名字)
3. 导入数据(把没有变的数据导入到创建的表中)  
4. 删除临时表 (删除第一步中创建的临时表)  
  通过以上四个步骤,就可以完成旧数据库结构向新数据库结构的迁移,并且其中还可以保证数据不会应为升级而流失。

2. 简单实例说明

实例是基于GreenDAO数据库框架。我是直接在下面的链接代码基础上面改的。

比如我们之前发布过一个1版本的数据库,现在要发布一个2版本的数据库。2版本在1版本上面做了一些小小的修改。

1版本的数据库的情况。(两个表表名是USER, HOME_SHARE_UPLOADE,默认的表名)

/** db version 1 */
        private static void addCloudAccounts(Schema schema) {
            Entity accounts = schema.addEntity("User");
            accounts.addStringProperty("password").notNull();
            accounts.addStringProperty("username").notNull().primaryKey();
            accounts.addBooleanProperty("rememberPassword");
        }

        private static void addHomeShareUploadTableInfo(Schema schema) {
            Entity uploadInfo = schema.addEntity("HomeShareUploadInfo");
            uploadInfo.addIdProperty().autoincrement();
            uploadInfo.addStringProperty("filename").notNull();
            uploadInfo.addStringProperty("md5").notNull();
            uploadInfo.addStringProperty("from").notNull();
            uploadInfo.addStringProperty("to").notNull();
            uploadInfo.addDateProperty("uploadTime").notNull();
            uploadInfo.addIntProperty("percent");
            uploadInfo.addIntProperty("state");
            uploadInfo.addIntProperty("type");
        }

现在要升级到2数据库的版本。做的改动如下(1. 删掉了HOME_SHARE_UPLOADE表,2. 修改了表的名字USER 改为usernewtable,3. 删掉了原来rememberPassword列,4. 新增了gender列。。我们就分这四步来update第三步稍微复杂一点点)

/**
     * db version 2 delete two column and add a column
     */
    private static void addCloudAccounts(Schema schema) {
        Entity accounts = schema.addEntity("User");
        accounts.setTableName("usernewtable");
        accounts.addStringProperty("password").notNull();
        accounts.addStringProperty("username").notNull().primaryKey();
        accounts.addBooleanProperty("gender");
    }

GreenDAO数据库框架对应的数据库升级的代码在自动生成的代码DaoMaster类中DevOpenHelper类的方法中重写。

@Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
            for (int index = oldVersion; index < newVersion; index ++) {
                switch (index) {
                    case 1:
                        db.beginTransaction();
                        try {
                            /** 1. drop HOME_SHARE_UPLOAD_INFO table */
                            db.execSQL("DROP TABLE IF EXISTS HOME_SHARE_UPLOAD_INFO;");


                            /** 2. change table name (USER -> usernewtable) */
                            db.execSQL("ALTER TABLE USER RENAME TO usernewtable;");

                            /** now table name is usernewtable */
                            /** 3. delete one column (REMEMBERPASSWORD) */
                            /** (Change the table name to temporary tables) temp table usernewtable_backup */
                            db.execSQL("ALTER TABLE usernewtable RENAME TO usernewtable_backup;");
                            /** (Create new table) */
                            db.execSQL("CREATE TABLE usernewtable(PASSWORD TEXT NOT NULL, USERNAME TEXT PRIMARY KEY NOT NULL);");
                            /** (Import data) */
                            db.execSQL("INSERT INTO usernewtable SELECT PASSWORD, USERNAME FROM usernewtable_backup;");
                            /** (Delete temp table) */
                            db.execSQL("DROP TABLE usernewtable_backup;");


                            /** 4. add one column */
                            db.execSQL("ALTER TABLE usernewtable ADD COLUMN GENDER INTEGER;");
                            db.setTransactionSuccessful();
                        } finally {
                            db.endTransaction();
                        }
                        break;
                }
            }
        }

        @Override
        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            /** down grade then recreate table */
            dropAllTables(db, true);
            onCreate(db);
        }