一.數據庫升級:
在我們的程序中,或多或少都會涉及到數據庫,使用數據庫必定會涉及到數據庫的升級,數據庫升級帶來的一些問題,如舊版本數據庫的數據記錄的保持,對新表的字段的添加等等一系列問題,還記得當我來西安的時候,面試的第二家公司,做音樂播放客戶端的,就問到了這個問題;
我們開發了一個程序,當前是1.0版本。該程序用到了數據庫。到1.1版本時,在數據庫的某個表中增加了一個字段。那么軟件1.0版本用的數據庫在軟件1.1版本就要被升級了。軟件的1.0版本升級到1.1版本時,老的數據不能丟。那么在1.1版本的程序中就要有地方能夠檢測出來新的軟件版本與老的數據庫不兼容,並且把1.0軟件的數據庫升級到1.1軟件能夠使用的數據庫。也就是說,要在1.0軟件的數據庫的那個表中增加那個字段,並賦予這個字段默認值。
程序如何知道我們的數據庫需要升級呢?SQLiteOpenHelper類的構造函數有一個參數是version即數據庫版本號。比如在軟件1.0版本中,我們使用SQLiteOpenHelper訪問數據庫時,該參數為1,那么數據庫版本號1就會寫在我們的數據庫中。到了1.1版本,我們的數據庫需要發生變化,那么我們1.1版本的程序中就要使用一個大於1的整數來構造SQLiteOpenHelper類,用於訪問新的數據庫,比如2。當我們的1.1新程序讀取1.0版本的老數據庫時,就發現老數據庫里存儲的數據庫版本是1,而我們新程序訪問它時填的版本號為2,系統就知道數據庫需要升級。
當系統在構造SQLiteOpenHelper類的對象時,如果發現版本號不一樣,就會自動調用onUpgrade函數,在這個方法里對數據庫進行升級。在這個函數中把老版本數據庫的相應表中增加字段,並給每條記錄增加默認值即可。新版本號和老版本號都會作為onUpgrade函數的參數傳進來,便於開發者知道數據庫應該從哪個版本升級到哪個版本。升級完成后,數據庫會自動存儲最新的版本號為當前數據庫版本號。
SQLite提供了ALTER TABLE命令,允許用戶重命名或添加新的字段到已有表中,但是不能從表中刪除字段。並且只能在表的末尾添加字段,比如,為Test添加一個字段:"ALTER TABLE Test ADDCOLUMN age"
下面是我的一個測試:
public class DataSQL extends SQLiteOpenHelper { public DataSQL(Context context, int version) { super(context, "Test", null, version); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table test(id integer primary key autoincrement, name)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (newVersion == 2) { db.execSQL("ALTER TABLE test ADD COLUMN age"); Cursor cr = db.rawQuery("select * from test", null); while (cr.moveToNext()) { String name = cr.getString(cr.getColumnIndex("name")); ContentValues values = new ContentValues(); values.put("name", name); values.put("age", 23); db.update("test", values, "name=?", new String[] { name }); } } } }
添加數據及讀取數據
DataSQL sql = new DataSQL(this, 2); SQLiteDatabase db = sql.getWritableDatabase(); Cursor cr = db.query("test", null, null, null, null, null, null); while (cr.moveToNext()) { Log.e("cr-name", "" + cr.getString(cr.getColumnIndex("name"))); Log.e("cr-age", "" + cr.getInt(cr.getColumnIndex("age"))); }
我只添加了一條數據,可以看到這條數據被打印出來了.
如果遇到復雜的修改操作,比如在修改的同時,需要進行數據的轉移,那么可以采取在一個事務中執行如下語句來實現修改表的需求。
1. 將表名改為臨時表
ALTERTABLE Test RENAME TO _Test;
2. 創建新表
CREATETABLE Test(id VARCHAR(32) PRIMARYKEY ,UserName VARCHAR(32) NOTNULL , Age VARCHAR(16) NOTNULL);
3. 導入數據
INSERTINTO Test SELECT id, “”, Age FROM _Test;
或者
INSERTINTO Test() SELECT id, “”, Age FROM _Test;
* 注意 雙引號”” 是用來補充原來不存在的數據的!
4. 刪除臨時表
DROPTABLE _Test;
通過以上四個步驟,就可以完成舊數據庫結構向新數據庫結構的遷移,並且其中還可以保證數據不會因為升級而流失。
當然,如果遇到減少字段的情況,也可以通過創建臨時表的方式來實現。
下面仍然通過一個例子來進行測試:
1.修改DataSQL的onUpgrade方法
if (newVersion == 3) { char str = '"'; db.beginTransaction(); db.execSQL("ALTER TABLE test RENAME TO _Test"); db.execSQL("CREATE TABLE test(id integer primary key autoincrement , PassWord VARCHAR(20) NOT NULL," + " UserName VARCHAR(32) NOT NULL , Age VARCHAR(16) NOT NULL)"); db.execSQL("INSERT INTO test SELECT id, " + str + str + ", name, age FROM _Test"); db.setTransactionSuccessful(); db.endTransaction(); }
2.修改Activity中打印信息
Log.e("cr-name", "" + cr.getString(cr.getColumnIndex("UserName"))); Log.e("cr-age", "" + cr.getInt(cr.getColumnIndex("Age"))); Log.e("cr-password", "" + cr.getInt(cr.getColumnIndex("PassWord")));
在實際開發工作中,我們的處理可能比上面所述的復雜;
假如我們開發的程序已經發布了兩個版本:V1.0,V1.2,我們正在開發V1.3。每一版的數據庫版本號分別是8,9,10。對於這種情況,我們應該如何實現升級?
用戶的選擇有:
1) V1.0 -> V1.3 DB 8 -> 10
2) V1.2 -> V1.3 DB 9 -> 10
3)注意:數據庫的每一個版本所代表的數據庫必須是定義好的,比如說V1.0的數據庫,它可能只有兩張表TableA和TableB,如果V1.2要添加一張表TableC,如果V1.3要修改TableC,那么每一個版本所對應的數據庫結構如下:
V1.0 ---> TableA, TableB
V1.2 ---> TableA, TableB, TableC
V1.3 ---> TableA, TableB, TableC (Modify)
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { int upgradeVersion = oldVersion; if (8 == upgradeVersion) { // Create table C String sql = "CREATE TABLE ..."; db.execSQL(sql); upgradeVersion = 9; } if (9 == upgradeVersion) { // Modify table C upgradeVersion = 10; } if (upgradeVersion != newVersion) { // Drop tables db.execSQL("DROP TABLE IF EXISTS " + tableName); // Create tables onCreate(db); } }
在onUpgrade()方法中,處理了數據庫版本從8 -> 10的升級過程,這樣做的話,不論用戶從8 -> 10,還是從9 - 10,最終程序的數據庫都能升級到V1.3所對應的數據庫結構。
二.導入已有數據庫
在有的情況下,我們要在程序一開始運行的時候就導入某些固定的數據,而這些數據過大,又不可能直接代碼寫死,此時就需要通過導入已有數據庫的方法導入數據,我們知道raw文件夾下的東西,android會原封不動的拷貝到程序中,而不會轉換為二進制文件,所以,我們把數據庫放到raw文件夾下供程序導入使用;
public class DBImporter { public static final String PACKAGE_NAME = "com.example.sql"; public static final String DB_NAME = "xxx.db"; public static String DB_PATH = "/data/data/" + PACKAGE_NAME; private Context context; public DBImporter(Context mContext) { this.context = mContext; } public SQLiteDatabase openDataBase() { return SQLiteDatabase.openOrCreateDatabase(DB_PATH + "/" + DB_NAME, null); } public void copyDB() { File file = new File(DB_PATH + "/" + DB_NAME); if (!file.exists()) { try { FileOutputStream out = new FileOutputStream(file); int buffer = 400000; // 讀取數據庫並保存到data/data/packagename/xx.db...
InputStream ins = context.getResources().openRawResource(R.raw.sql_); byte[] bts = new byte[buffer]; int length; while ((length = ins.read(bts)) > 0) { out.write(bts, 0, bts.length); } out.close(); ins.close(); SQLiteDatabase.openOrCreateDatabase(DB_PATH + "/" + DB_NAME, null); } catch (FileNotFoundException e) { } catch (IOException e) { } } } }
接下來便是在需要使用到該數據庫的地方調用
DBImporter importer = new DBImporter(this); importer.copyDB(); SQLiteDatabase db = importer.openDataBase(); // 獲取到數據庫對象,接下來便可操作了~