android mysql移植_sqlite升级--浅谈Android数据库版本升级及数据的迁移

本文探讨了Android开发中数据库升级的问题,包括增加、删除和修改表的情况。重点介绍了如何在升级过程中进行数据迁移,避免用户数据丢失,提供良好的用户体验。通过示例展示了基于ORMLite库的数据库版本管理,包括onCreate()和onUpgrade()方法的使用,并提供了数据迁移的实用方法和策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android开发涉及到的数据库采用的是轻量级的SQLite3,而在实际开发中,在存储一些简单的数据,使用SharedPreferences就足够了,只有在存储数据结构稍微复杂的时候,才会使用数据库来存储。而数据库表的设计往往不是一开始就非常完美,可能在应用版本开发迭代中,表的结构也需要调整,这时候就涉及到数据库升级的问题了。

数据库升级

数据库升级,主要有以下这几种情况:

增加表

删除表

修改表

增加表字段

删除表字段

增加表和删除表问题不大,因为它们都没有涉及到数据的迁移问题,增加表只是在原来的基础上CRTATE TABLE,而删除表就是对历史数据不需要了,那只要DROP TABLE即可。那么修改表呢?

其实,很多时候,程序员为了图个方便,最简单最暴力的方法就是,将原来的表删除了然后重新创建新的表,这样就不用考虑其他因素了。但这样对于用户来说,体验是非常不好的,比如:用户当前下载列表正在下载文件,此时进行更新,而新版本有个更新点是升级了下载列表的数据库表,那么用户更新完之后发现下载列表变空了,那么用户看到辛辛苦苦下载的99%文件.avi没来,那不崩溃了,这种体验是非常不好的,分分钟就卸载你的应用。

那么数据库表升级时,数据迁移就显得非常重要了,那么如何实现呢?

表升级,数据迁移

现在开发,为了效率,都会使用第三方,本文数据库方面是基于ORMLite的,所以接下来讨论的都是基于此。

1 ->2 ->3

A

A+

A

B

B-

B

C

C

C+

上表的意思是:版本升级从版本号1升级到2再升级到3,1->2->3,期间表ABC的变化,‘+’表示该表增加了字段,‘-’表示该表删除了字段,例如1升级到2,表A增加了字段,表B删除了字段,表C没有发生变化。

首先,我们要先理解SQLiteOpenHelper中

/**

* Called when the database is created for the first time. This is where the

* creation of tables and the initial population of the tables should happen.

*

*@param db The database.

*/

public abstract void onCreate(SQLiteDatabase db);

/**

* Called when the database needs to be upgraded. The implementation

* should use this method to drop tables, add tables, or do anything else it

* needs to upgrade to the new schema version.

*@param db The database.

*@param oldVersion The old database version.

*@param newVersion The new database version.

*/

public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);

什么时候调用。文档说得很清楚了,onCreate()是数据库第一次创建的时候调用,而onUpgrade()是当数据库版本升级的时候调用。

首先,先简单的创建A、B、C三个类,并使用OrmLite注解来创建表

A.class

import com.j256.ormlite.field.DatabaseField;

import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable(tableName = "tb_a")

public class A {

@DatabaseField(generatedId = true)

public int id;

@DatabaseField

public String name;

}

B.class

import com.j256.ormlite.field.DatabaseField;

import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable(tableName = "tb_b")

public class B {

@DatabaseField(generatedId = true)

public int id;

@DatabaseField

public String name;

@DatabaseField

public String age;

}

C.class

import com.j256.ormlite.field.DatabaseField;

import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable(tableName = "tb_c")

public class C {

@DatabaseField(generatedId = true)

public int id;

@DatabaseField

public String name;

}

创建自己的Helper的MySqliteHelper.class

import android.content.Context;

import android.database.sqlite.SQLiteDatabase;

import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;

import com.j256.ormlite.support.ConnectionSource;

import com.j256.ormlite.table.TableUtils;

import java.sql.SQLException;

public class MySqliteHelper extends OrmLiteSqliteOpenHelper{

private final static String DATABASE_NAME="test.db";

private final static int DATABASE_VERSION = 1;

private static MySqliteHelper mInstance;

public MySqliteHelper(Context context) {

super(context, DATABASE_NAME, null, DATABASE_VERSION);

}

public static MySqliteHelper getInstance(Context context) {

if (mInstance == null) {

mInstance= new MySqliteHelper(context);

}

return mInstance;

}

@Override

public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {

try {

TableUtils.createTableIfNotExists(connectionSource,A.class);

TableUtils.createTableIfNotExists(connectionSource,B.class);

TableUtils.createTableIfNotExists(connectionSource,C.class);

} catch (SQLException e) {

e.printStackTrace();

}

}

@Override

public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {

}

}

创建数据操作的Dao

import android.content.Context;

import com.j256.ormlite.dao.Dao;

import java.sql.SQLException;

public class ADao {

private Dao dao;

public ADao(Context context){

try {

dao = MySqliteHelper.getInstance(context).getDao(A.class);

} catch (SQLException e) {

e.printStackTrace();

}

}

}

BDao、CDao,也类似。

运行程序,进行Dao操作,此时就创建数据库test.db,进而执行onCreate()创建表。

import android.app.Application;

import android.test.ApplicationTestCase;

import android.test.suitebuilder.annotation.MediumTest;

import com.helen.andbase.demolist.db.A;

import com.helen.andbase.demolist.db.ADao;

public class ApplicationTest extends ApplicationTestCase {

public ApplicationTest() {

super(Application.class);

}

@MediumTest

public void testDao(){

ADao aDao = new ADao(getContext());

A a = new A();

a.name="a";

aDao.add(a);

BDao bDao = new BDao(getContext());

B b = new B();

b.name="a";

b.age ="18";

bDao.add(b);

}

}

2f29828af4ff8ba65f32018d144b2698.png

将其拷出来,查看数据库。这里使用SQLiteExpertPers进行查看

cf58e4300b3091a7cf9a8951fa307972.png

a4c1beeda953c7a2e89765cb976b9516.png

如上图表已创建。接着我们进行数据库升级,将版本号DATABASE_VERSION变为2,表A新增字段age,表B删除字段age,C不变

@DatabaseTable(tableName = "tb_a")

public class A {

@DatabaseField(generatedId = true)

public int id;

@DatabaseField

public String name;

@DatabaseField

public String age;

}

@DatabaseTable(tableName = "tb_b")

public class B {

@DatabaseField(generatedId = true)

public int id;

@DatabaseField

public String name;

}

@DatabaseTable(tableName = "tb_c")

public class C {

@DatabaseField(generatedId = true)

public int id;

@DatabaseField

public String name;

}

简单暴力的解决方法是:

@Override

public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {

if(oldVersion < 2){//暂不说明为何要这么判断

try {

TableUtils.dropTable(connectionSource,A.class,true);

TableUtils.dropTable(connectionSource,B.class,true);

} catch (SQLException e) {

e.printStackTrace();

}

}

onCreate(db,connectionSource);

}

先将旧的表删除再创建新的表,这是最简单暴力的,但前面提过这不是我们想要的结果。

将代码改下,

@Override

public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {

if(oldVersion < 2){//暂不说明为何要这么判断

DatabaseUtil.upgradeTable(db,connectionSource,A.class,DatabaseUtil.OPERATION_TYPE.ADD);

}

onCreate(db,connectionSource);

}

主要的代码就是封装的DatabaseUtil.class这个类

import android.database.Cursor;

import android.database.sqlite.SQLiteDatabase;

import com.j256.ormlite.misc.JavaxPersistence;

import com.j256.ormlite.support.ConnectionSource;

import com.j256.ormlite.table.DatabaseTable;

import com.j256.ormlite.table.TableUtils;

import java.util.Arrays;

public class DatabaseUtil {

public static final String TAG = "DatabaseUtil.java";

/**数据库表操作类型*/

public enum OPERATION_TYPE{

/**表新增字段*/

ADD,

/**表删除字段*/

DELETE

}

/**

* 升级表,增加字段

*@param db

*@param clazz

*/

public static void upgradeTable(SQLiteDatabase db,ConnectionSource cs,Class clazz,OPERATION_TYPE type){

String tableName = extractTableName(clazz);

db.beginTransaction();

try {

//Rename table

String tempTableName = tableName + "_temp";

String sql = "ALTER TABLE "+tableName+" RENAME TO "+tempTableName;

db.execSQL(sql);

//Create table

try {

sql = TableUtils.getCreateTableStatements(cs, clazz).get(0);

db.execSQL(sql);

} catch (Exception e) {

e.printStackTrace();

TableUtils.createTable(cs, clazz);

}

//Load data

String columns;

if(type == OPERATION_TYPE.ADD){

columns = Arrays.toString(getColumnNames(db,tempTableName)).replace("[","").replace("]","");

}else if(type == OPERATION_TYPE.DELETE){

columns = Arrays.toString(getColumnNames(db,tableName)).replace("[","").replace("]", "");

}else {

throw new IllegalArgumentException("OPERATION_TYPE error");

}

sql = "INSERT INTO "+tableName +

" ("+ columns+") "+

" SELECT "+ columns+" FROM "+tempTableName;

db.execSQL(sql);

//Drop temp table

sql = "DROP TABLE IF EXISTS "+tempTableName;

db.execSQL(sql);

db.setTransactionSuccessful();

}catch (Exception e){

e.printStackTrace();

}finally {

db.endTransaction();

}

}

/**

* 获取表名(ormlite DatabaseTableConfig.java)

*@param clazz

*@param

*@return

*/

private static String extractTableName(Class clazz) {

DatabaseTable databaseTable = clazz.getAnnotation(DatabaseTable.class);

String name ;

if (databaseTable != null && databaseTable.tableName() != null && databaseTable.tableName().length() > 0) {

name = databaseTable.tableName();

} else {

/*

* NOTE: to remove javax.persistence usage, comment the following line out

*/

name = JavaxPersistence.getEntityName(clazz);

if (name == null) {

// if the name isn't specified, it is the class name lowercased

name = clazz.getSimpleName().toLowerCase();

}

}

return name;

}

/**

* 获取表的列名

*@param db

*@param tableName

*@return

*/

private static String[] getColumnNames(SQLiteDatabase db,String tableName){

String[] columnNames = null;

Cursor cursor = null;

try {

cursor = db.rawQuery("PRAGMA table_info("+tableName+")",null);

if(cursor != null){

int columnIndex = cursor.getColumnIndex("name");

if(columnIndex == -1){

return null;

}

int index = 0;

columnNames = new String[cursor.getCount()];

for(cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()){

columnNames[index] = cursor.getString(columnIndex);

index++;

}

}

}catch (Exception e){

e.printStackTrace();

}finally {

if(cursor != null) {

cursor.close();

}

}

return columnNames;

}

}

upgradeTable方法里采用的是数据库事务,利用事务的原子特性,保证所有的SQL能全部执行完成。主要思路是:首先将原来的表进行改名称rename table(临时表),接着创建新的表create table,再者将旧表内的数据迁移到新表内,最后drop table删除临时表。

表的增加字段和删除字段,在迁移数据的时候,主要区别在于字段的来源不同。比如:A表新增了age字段,这时候columns变量的获取是根据旧表来的,这是构造的sql语句是

sql = "INSERT INTO tb_a (id,name) SELECT id,name FROM tb_a_temp";

1

1

而B表是删除age字段的,columns变量的获取是根据新表来的,其构造的sql语句是

sql = "INSERT INTO tb_b (id) SELECT id FROM tb_b_temp";

1

1

再次执行ApplicationTest->testDao

@MediumTest

public void testDao(){

ADao aDao = new ADao(getContext());

A a = new A();

a.name="a";

a.age = "20";

aDao.add(a);

BDao bDao = new BDao(getContext());

B b = new B();

b.name="b";

bDao.add(b);

}

再查看下数据

97684420bf411cbbd17e48c7740574d9.png

e446d0db9f569476dedc75e1398a3e65.png

可以看到表A、表B的历史数据还是存在的。

然后我们再将数据库升级到版本号为3,这时候要考虑到用户的多种情况,从1->3,从2->3这两种情况,但不是每次升级都重复之前的操作的,比如用户1之前已经从1升级到2了,这次是要从2升级到3,而用户2,一直用的是老版本1,他觉得,嗯,这次这个版本升级的内容不错,决定升级了,那么他是从1直接升级到3的,所以他们两者经历的版本不一样,数据库升级的策略也会有所不用,那就要区分开来考虑了。

这次的升级是将表C添加了sex字段

import com.j256.ormlite.field.DatabaseField;

import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable(tableName = "tb_c")

public class C {

@DatabaseField(generatedId = true)

public int id;

@DatabaseField

public String name;

@DatabaseField

public String sex;

}

然后在onUpgrade进行逻辑判断

@Override

public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {

if(oldVersion < 2){

DatabaseUtil.upgradeTable(db,connectionSource,A.class,DatabaseUtil.OPERATION_TYPE.ADD);

DatabaseUtil.upgradeTable(db,connectionSource,B.class,DatabaseUtil.OPERATION_TYPE.DELETE);

}

if(oldVersion < 3){

DatabaseUtil.upgradeTable(db,connectionSource,C.class,DatabaseUtil.OPERATION_TYPE.ADD);

}

onCreate(db,connectionSource);

}

这样,如果你是从1升级到3,那么两个if语句都会执行,而如果是从2升级到3,那么只有if(oldVersion < 3)这个分支会执行。最后,如果只是新增全新的表D,那么只要在onCreate内多写句TableUtils.createTableIfNotExists(connectionSource, D.class);就可以啦,不要忘记版本号要+1~

总结

本文讨论的数据迁移,是基于新旧两个表之间逻辑性不强,不牵涉到业务情景的情况下。比如,表A新增的字段user_id为用户id,这个字段是用来标记数据来源于哪个用户的,检索的时候,user_id是用于检索条件的,那么由于旧数据转移到新表中user_id默认是空的,这时候旧数据可能相当于不起作用了,虽然可以通过设置默认值,但其需要根据具体业务场景进行设置,因此就失去其灵活性了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值