天天看點

Android資料庫無縫更新方案

軟體疊代過程中,業務不斷更新,也要求軟體持續更新。相應地,資料庫更新更新也是不可避免的一個環節。Android作為用戶端應用,資料庫更新相對于服務端來說會麻煩一些。常見的更新方式有:

  1.删除舊表和資料,建立新表。優點是簡單友善,缺點是丢失了舊資料。适用于應用資料依賴度低的情況。

  2.在代碼中相容處理各版本資料庫,建立新表,遷移舊資料到新表。優點是保留了舊資料,缺點是需要處理相容個版本資料庫差異,比較麻煩。如果通過代碼來記錄維護版本差異,會導緻代碼臃腫且極易出錯。

本文介紹一種簡單無縫的資料庫更新方案,也是屬于上述的第二種方式,但是簡單、高效地處理了資料庫版本相容。代碼已實作在DBFramework中,這是一個輕量的資料庫架構,簡單、規範、高效,能夠很好的處理表之間的繼承關系,可以為你節省很多開發工作。

感興趣的朋友可以了解一下。

資料庫更新,其實是對其中的表進行更新。要更新表并且保留資料,這就必須要知道新表和舊表之間的差異。如果我們靠開發人員來記錄各版本之間表的差異,無疑是一個困難和繁雜的工作。那麼當我們進行資料庫的時候,用戶端應用有沒有辦法知道這些差異,知道有哪些表需要更新呢?答案是肯定的。在Sqlite中有一張叫做sqlite_master的系統表,其中記錄了sqlite中的所有表資訊,如下:

Android資料庫無縫更新方案
可以看到,其中除了表名,還有表建立語句。有了建立語句,我們就能解析出表的字段資訊,然後對比新表和舊表的字段,有差異則說明表需要更新,反之則不需要更新。如下代碼用來擷取現有的表資訊:

/**
     * Get old tables.
     * @return A map contains old tables which takes table name as key.
     */
    public Map<String, Table> getOldTables() {
        final String table = "sqlite_master";
        final String[] columns = {"name", "sql"};
        Cursor cursor = mDB.query(table, columns, "type='table'", null, null, null, null);
        HashMap<String, Table> ret = null;
        if (cursor.getCount() > 0) {
            ret = new HashMap<String, Table>(cursor.getCount());
            Table tmp;
            while (cursor.moveToNext()) {
                tmp = new Table(cursor.getString(0), cursor.getString(1));
                ret.put(tmp.Name, tmp);
            }
        }
        cursor.close();
        return ret == null ? Collections.EMPTY_MAP : ret;
    }      

Table是一個記錄表資訊的類,拿到現有表資訊,就可以和新的表資訊進行對比,主要對比表字段的變化:

public class Table {

   ...

  public boolean equalsColumns(Table table) {
        String myColumns = getColumnsStatement(CreateSql).toUpperCase();
        String columns = getColumnsStatement(table.CreateSql).toUpperCase();
        return (myColumns.equals(columns));
    }

   ...

}      

 如果無變化則跳過,有變化則更新,更新過程為:

1.将現有表以别名命名

2.建立新表

3.遷移資料

4.删除現有表

 如下代碼所示:

@Override
    protected void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion, List<Table> tableList) {
        Map<String, Table> oldMap = getOldTables();
        Iterator<Table> iterator = tableList.iterator();
        Table table;
        Table oldTable;
        String tempTable;
        while (iterator.hasNext()) {
            table = iterator.next();
            if ((oldTable = oldMap.get(table.Name)) == null) {
                //New table, create directly.
                db.execSQL(table.CreateSql);
                Log.i(TAG, "Table " + table.Name + " is a new table. Create it directly");
                continue;
            }
            //Remove hit table.
            oldMap.remove(table.Name);
            if (oldTable.equalsColumns(table)) {
                //Table not change.
                Log.i(TAG, "Table " + table.Name + " doesn't need update");
                continue;
            }
            tempTable = table.Name + TEMP_SUFFIX;
            Log.i(TAG, "Update table: " + table.Name);

            //Table changed.
            //Alter old table as temp.
            alterTableName(oldTable.Name, tempTable);
            //Create new table.
            db.execSQL(table.CreateSql);
            //Copy data.
            copyData(getCommonColumn(oldTable.getColumns(), table.getColumns()), tempTable, table.Name);
            //Delete old table
            deleteTable(tempTable);
        }
        //Delete obsolete tables not hit.
        deleteObsoleteTables(oldMap.values());
    }      

更多細節可檢視源碼:DBFramework。

這種方案簡單友善,而且幾乎一勞永逸。推薦小夥伴們使用,如果有更好的方式或者寶貴意見,歡迎交流。