Android 数据库升级中数据保持和导入已有数据库
一.数据库升级:
在我们的程序中,或多或少都会涉及到数据库,使用数据库必定会涉及到数据库的升级,数据库升级带来的一些问题,如旧版本数据库的数据记录的保持,对新表的字段的添加等等一系列问题,还记得当我来西安的时候,面试的第二家公司,做音乐播放客户端的,就问到了这个问题;
我们开发了一个程序,当前是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(); // 获取到数据库对象,接下来便可操作了~