Android数据库迁移(升级)你想要的都在这里!

背景介绍

公司要对一个运营了4年之久的app重构,其中重点强调了数据库的表结构和字段的优化,那么问题来了,重构之后的版本数据库名字叫andHe.db(新db),老版本的数据库名字是rcs.db(旧db),这两个数据库大部分表大部分内容是差不多的,不同的地方表现在以下方面:

假设新db有C、D、E、F表,旧db有A、B、C、D表,那么新db与旧db就有common表C、D。

问题一:即使common表名字都是C、D,其内部的字段也仅仅是部分相同,可能新C表在旧C表的基础上删除了一些无用字段并且新增了一些字段;

问题二:即使是同名表,例如新C表与旧C表在common字段上的排序也完全不同,举个栗子?

新表与旧表字段对比(旧表标红部分为删除字段,新表标红部分为新增字段)

问题三:原来操作数据库包括建表、增、删、改查全部用的原生sql语句,但是重构之后的版本是用greendao对数据库进行操作

可能大部分同学拿到这样的需求第一反应就是WTF??为什么你升级还要改db名字,而且即使是同一张表,字段
顺序你全部都打乱了,也许刚开始我们都不想接这个茬,奈何我们是板砖的呢,只能硬着头皮往下研究了…

方案研究

方案一:从旧db中读取全部数据用集合存起来再插入到新db中(common字段手动查询出来并写死);

通过cursor读取原数据库common表(相同的表)common字段(相同的字段)的值,再用greendao插入到新db中

方案二:从旧db中读取全部数据用集合存起来再插入到新db中

通过cursor读取新旧db里的表遍历出两个db中的common表,再逐个比较common表获取common字段,取出来原common字段的值再插入到新的表中;

方案三:通过读取新旧db里的表遍历查询出两个db中共有的表,再比较共有的表获取相同的字段,直接用sql语句查询原表common字段并插入到新表;

这三个方案可以说是我踩坑的历程,也是一个数据库迁移方案优化的过程,下面我就这三个方案详细说明一下:

方案一

既然是cursor查和greendao插入,那么思路很明确,我也仅贴出一个common表对应的bean类的操作过程(代码):

private void queryCallMark() {
    Cursor cursor = mContext.getContentResolver().query(Conversations.CallLogMark.CONTENT_URI, null, null, null, null);
    if (cursor != null && cursor.getCount() > 0) {
        callMarks = new ArrayList<>(cursor.getCount());
        try {
            while (cursor.moveToNext()) {
                CallMark callMark = new CallMark();

                long _id = cursor.getLong(cursor.getColumnIndex("_id"));//_id
                int is_mark = cursor.getInt(cursor.getColumnIndex("is_mark"));//is_mark
                String mark_content = cursor.getString(cursor.getColumnIndex("mark_content"));//mark_content
                int mark_type = cursor.getInt(cursor.getColumnIndex("mark_type"));//mark_type
                String address = cursor.getString(cursor.getColumnIndex("address"));//address
                int address_id = cursor.getInt(cursor.getColumnIndex("address_id"));//address_id
                String contribution_id = cursor.getString(cursor.getColumnIndex("contribution_id"));//contribution_id
                String conversation_id = cursor.getString(cursor.getColumnIndex("conversation_id"));//conversation_id
                long date = cursor.getLong(cursor.getColumnIndex("date"));//date
                String person_letter = cursor.getString(cursor.getColumnIndex("person_letter"));//person_letter
                long notify_date = cursor.getLong(cursor.getColumnIndex("notify_date"));//notify_date
                String person = cursor.getString(cursor.getColumnIndex("person"));//person
                String person_pinyin = cursor.getString(cursor.getColumnIndex("person_pinyin"));//person_pinyin
                String send_address = cursor.getString(cursor.getColumnIndex("send_address"));//send_address
                long timestamp = cursor.getLong(cursor.getColumnIndex("timestamp"));//timestamp
                int type = cursor.getInt(cursor.getColumnIndex("type"));//type

                callMark.setId(_id);
                callMark.setIsMark(is_mark);
                callMark.setMarkContent(mark_content);
                callMark.setMarkType(mark_type);
                callMark.setNumber(address);
                callMark.setAddressId(address_id);
                callMark.setContributionId(contribution_id);
                callMark.setConversationId(conversation_id);
                callMark.setDate(date);
                callMark.setLetter(person_letter);
                callMark.setNotifyDate(notify_date);
                callMark.setPerson(person);
                callMark.setPinyin(person_pinyin);
                callMark.setSendAddress(send_address);
                callMark.setTimestamp(timestamp);
                callMark.setType(type);

                Log.d(TAG, "queryCallMark: " + callMark.toString());
                callMarks.add(callMark);
            }

        } catch (Exception e) {
            Log.e(TAG, "e:" + e);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }
}

对应的插入使用greendao

if (callMarks != null) {
    for (CallMark callMark : callMarks) {
        callMarkDao.insert(callMark);
    }
}

乍一看好像没有什么问题,经测试发现大部分情况下是ok的,但某些时候会插入失败,查看具体的log是提示查询的时候旧表或新表没有该字段,这让我突然意识到可能旧表的部分字段是动态添加的,而我pull出来的新、旧db可能比较出来的common字段并不完整,究其原因是我刚开始迁移的时候该字段还没有生成,这样的话查询旧表获取不到该字段,插入新表也没有该字段,于是应用就无情的crash了;

还有一个问题也是导致我放弃方案一的原因,数据库迁移必须兼容老版本,而我模拟的是当前最新版本,假如当前最新version 29(重构版本是version 30),因此线上大部分用户是version 29,但不排除部分用户是version 28,version 27…这样的话跨版本升级,我的这个方案就要在代码中写很多if else做版本兼容,
显然这样的代码越到后面越没法维护,于是只能想办法去动态的获取common表和common字段,然后方案二就来了。

方案二

在Sqlite中有一张叫做sqlite_master的系统表,其中记录了sqlite中的所有表信息,如下:

可以看到,其中就有所有的表名,但是可以看到type类型有index和table,我们需要过滤只获取type为table的所有值,对应的sql语句是

Cursor old_cursor = old_read_db.rawQuery("select name from sqlite_master where type='table' order by name", null);

获取到所有表以后就可以开始对比了,获取到新旧db所有的common表,再获取common表里的common字段,用集合,其核心思想可以总结为以下这个sql语句:

select * from first_table

insert into second_table (colomn1,column2...) values (value1,value2...)
注:其中value1、value2来自first_table,column1、column2是first_table与second_table的common字段

查询的时候没有发现什么问题,但是插入的时候问题就来了,主要有以下几个问题:
1、由于greendao在建表的时候给所有的INTEGER类型都添加了NOT NULL限制,而老项目里是直接用sql语句来操作android数据库的,并没有给INTEGER类型添加什么NOT NULL限制,cursor根据type类型取值。

再来看getType()方法:获取指定列数据类型,通过指定列编号来实现。android3.0后才支持。可以通过该方法获取当前数据列的数据数据类型,然后调用不同的方法来获取数据内容。
public abstract String getType()(int columnIndex)//参数columnIndex 为指定的列编号,返回值为指定列的数据类型,包括以下几种:
FIELD_TYPE_BLOB:二进制文件。
FIELD_TYPE_FLOAT:浮点型。
FIELD_TYPE_INTEGER:整型。
FIELD_TYPE_NULL:空值。
FIELD_TYPE_STRING:字符串。

sqlite 没有单独的 Boolean 存储类。相反,布尔值被存储为整数 0(false)和 1(true)。
sqlite 存入的是亲和类型(Affinity),也即存入的int和long类型的数据最后都会以INTEGER类型显示,取值的时候需要用cursor.getInt()或者getLong()加以区分,如果是long类型的数据你以int类型取出,最后插入数据时你会发现数据是负数,最常见的是time类型的数据。
sqlite 存入的字符串是文本字符串,而我们常用的字符串也即String类型中如含有特殊字符串单号’、双引号”“、反斜杠\等是需要进行转义的,如:

如需打印双引号需要用\进行转义,如:
String s = "\"";

而cursor.getString取出的String类型如果含有特殊字符串如双引号""
String ss = "特殊字符""";
再将ss这样的字符串用sqlite语句插入很快就会报syntax error near " ,原因也很简单,你没有对双引号进行转义

新表如果增加了INTEGER类型的字段我需要对这些新增的INTEGER类型的数据赋默认值,而common字段中如果有INTEGER类型且值且为null也需要单独过滤出来赋默认值,即使有值也要分int和long类型分别进行取值,INTEGER类型的数据我们尚且可以处理,但String类型的数据就不好处理了,总不能把遍历字符看有没有把特殊字符再转义吧,这个方案肯定也不行,于是方案三来了。

方案三

由于IOS的同事也要做数据库迁移,随后问了下IOS的同事,他们是怎么做数据库迁移的,发现他们也是用sqlite取原表的值再插入新表,唯一不同的是他们取出来的值不需要根据type转换成具体类型的值再插入,于是我就开始想android有没有不取出具体值的方法,我取出整体再整体插入,这样的话就避免了String类型的转义问题,最后查看是sqlite教程发现有一个语句可以实现我的方法:

使用一个表来填充另一个表
您可以通过在一个有一组字段的表上使用 select 语句,填充数据到另一个表中。下面是语法:
INSERT INTO first_table_name [(column1, column2, ... columnN)] 
   SELECT column1, column2, ...columnN 
   FROM second_table_name
   [WHERE condition];

这个语句唯一的限制就是查找和插入都是在同一个db里完成的,于是我又回头思考我是不是也可以在同一个db里做数据库迁移,这样不仅可以提高迁移效率,还可以解决取值的问题,具体的思路是这样的:
1、建立两个helper同时打开rcs.db和andHe.db,获取common表和common表中的common字段;
2、在旧的db也即rcs.db中创建temp表(假如Message表是其中一个common表,建立Message_TEMP),具体代码为:

private void generateTempTables(SQLiteDatabase db, List<String> commonTables) {
    for (int i = 0; i < commonTables.size(); i++) {
        String tempTableName = "";
        try {
            String tableName = commonTables.get(i);
            tempTableName = tableName.concat("_TEMP");
            StringBuilder dropTableStringBuilder = new StringBuilder();
            dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName).append(";");
            db.execSQL(dropTableStringBuilder.toString());

            StringBuilder insertTableStringBuilder = new StringBuilder();
            insertTableStringBuilder.append("CREATE TEMPORARY TABLE ").append(tempTableName);
            insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");
            db.execSQL(insertTableStringBuilder.toString());
            Log.d(TAG, "Table】" + tableName);
            Log.i(TAG, "Generate temp table" + tempTableName);
        } catch (SQLException e) {
            Log.e(TAG, "Failed to generate temp table" + tempTableName, e);
        }
    }
}

3、删除所有common表(数据都备份到temp表中了)

private void DropTables(SQLiteDatabase old_write_db, List<String> tables_should_delete) {
    for (String tableName : tables_should_delete) {
        String deleteSql = String.format("DROP TABLE IF EXISTS \"%s\"", tableName);
        old_write_db.execSQL(deleteSql);
    }
}

4、创建新表,这一步要注意下,前面由于greendao建表语句给INTEGER类型强加的NOT NULL限制,于是只能求助系统表sqlite_master,这个表里有建表语句sql,我可以获取这个sql改变它的建表语句去除NOT NULL限制并赋默认值-1;

private void createAllTables(SQLiteDatabase old_write_db, List<String> sqls) {
    for (String sql : sqls) {
        if (findStr(ANDROID_META_DATA, sql) || findStr(ANDROID_SEQUENCE, sql)) {
            continue;
        }
        String excuteSql = filterIntegerNotNull(sql);
        old_write_db.execSQL(excuteSql);
        Log.i(TAG, "createAllTables :" + excuteSql);
    }
}

//替换NOT NULL 为DEFAULT -1
private String filterIntegerNotNull(String sql) {
    return filterIntegerNotNull(sql, "INTEGER NOT NULL", "INTEGER DEFAULT -1").toString();
}

private boolean findStr(String src, String value) {
    Pattern p = Pattern.compile(src);
    Matcher m = p.matcher(value);
    return m.find();
}

private StringBuffer filterIntegerNotNull(String value, String src, String dest) {
    Pattern p = Pattern.compile(src);
    Matcher m = p.matcher(value);
    StringBuffer sb = new StringBuffer();
    boolean result = m.find();
    while (result) {//如果匹配成功就替换
        m.appendReplacement(sb, dest);
        result = m.find();//继续下一步匹配
    }
    m.appendTail(sb);
    return sb;
}

5、从temp表中copy数据到新表中(此时新表)

private void queryAndInsertData(String common_tableName, List<String> commonColumns) {
    Cursor oldColumn_cursor = null;
    try {
        oldColumn_cursor = old_read_db.rawQuery("select * from " + common_tableName, null);
        if (null == oldColumn_cursor && !oldColumn_cursor.moveToFirst()) {
            Log.e(TAG,String.format("table <%s> has no data !",common_tableName));
            return;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (oldColumn_cursor != null) {
            oldColumn_cursor.close();
        }
    }
    Log.d(TAG, "queryMessage same columns:" + commonColumns + ",count:" + commonColumns.size());

    final String querySql_columns = TextUtils.join(",", commonColumns);
    String tempTableName = common_tableName.concat("_TEMP");
    StringBuilder excuteSql = new StringBuilder();
    excuteSql.append("INSERT INTO ")
            .append(common_tableName)
            .append("(")
            .append(querySql_columns)
            .append(")")
            .append(" SELECT ")
            .append(querySql_columns)
            .append(" FROM ")
            .append(tempTableName);

    Log.i(TAG, "excuteSql:" + excuteSql.toString());
    old_write_db.execSQL(excuteSql.toString());

    Log.i(TAG,"Restore data to " + common_tableName);
}

6、删除temp表

    StringBuilder dropTableStringBuilder = new StringBuilder();
    dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
    old_write_db.execSQL(dropTableStringBuilder.toString());

    Log.d(TAG,"Drop temp table" + tempTableName);

7、数据迁移完了原来新建的andHe.db其实是空的,数据的迁移都是rcs.db中完成的,最后来个偷梁换柱把andHe.db删除,将rcs.db改名为andHe.db即可。

喜大普奔,数据迁移至此终于完成了!

But有2个遗留问题还是需要解决的:
1、我们是修改了sql的建表语句去掉了NOT NULL限制这才完成了数据迁移,如果后面的版本再新增了表又会出现NOT NULL限制,版本升级可能还有问题,这时候我只能寄希望于改greendao的源码然后将生成Dao类的代码改下,再将jar包放在公司自己布的mavean库上,一劳永逸。
2、整个迁移过程我们只关注了common表common字段,但实际业务可能是新增的表中部分字段可能取自原来废弃的表,这个时候就需要根据具体的业务做部分表的迁移了。

另外greendao有2个问题可能需要我们注意:
1、新建的model中字段id如果想要修改只能这么改,否则greendao不会根据你的属性id命名,且默认的是_id

@Entity(nameInDb = "t_book")
public class Book {
    @Property(nameInDb = "id")//通过@Property()这个注解定义数据库的字段名
    @Id(autoincrement = true)
    private Long id;
    private String f_book;

    @Generated(hash = 1839284504)
    public Book(Long id, String f_book) {
        this.id = id;
        this.f_book = f_book;
    }

    @Generated(hash = 1839243756)
    public Book() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getF_book() {
        return f_book;
    }

    public void setF_book(String f_book) {
        this.f_book = f_book;
    }
}

2、greendao建表时给INTEGER类型强加了NOT NULL限制并且没有默认值。

总结下:
1、多用sqlite语句可以直接明了的知道自己拼接的语句是不是正确的,一切为了效率;
2、如果思路堵塞,一定要多交流,一定。。。

最后附上关键的所有代码:

MigrationDBHelper.java

package greenDao.db;

import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import com.chinamobile.app.yuliao_business.util.DbUtils;
import com.chinamobile.app.yuliao_common.utils.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * this class is used to migrate db from rcs-%s.db to andHe-%s.db
 */

public class MigrationDBHelper {

    private static final String TAG = MigrationDBHelper.class.getSimpleName();

    private static final MigrationDBHelper instance = new MigrationDBHelper();
    private static final String ANDROID_META_DATA = "android_metadata";
    private static final String ANDROID_SEQUENCE = "sqlite_sequence";

    private static Context mContext;
    private String mPhoneNumber;

    private MyOpenHelper oldDbHelper;
    private MyOpenHelper newDbHelper;
    private SQLiteDatabase old_read_db;
    private SQLiteDatabase new_read_db;
    private SQLiteDatabase old_write_db;

    public static MigrationDBHelper getInstance(Context appContext) {
        MigrationDBHelper.mContext = appContext;
        return instance;
    }

    public synchronized void updateDatabases(String phoneNumber) {
        this.mPhoneNumber = phoneNumber;
        oldDbHelper = new MyOpenHelper(mContext, getOldDbName(), null);
        newDbHelper = new MyOpenHelper(mContext, getNewDbName(), null);

        old_read_db = oldDbHelper.getReadableDatabase();
        new_read_db = newDbHelper.getReadableDatabase();
        old_write_db = oldDbHelper.getWritableDatabase();

        Log.d(TAG, "queryMessage old_read_db:" + old_read_db.toString());
        Log.d(TAG, "queryMessage new_read_db:" + new_read_db.toString());

        startMigration();
    }

    private void startMigration() {
        Log.i(TAG, "<----migrateTables start---->");
        long start = System.currentTimeMillis();

        Cursor old_cursor = old_read_db.rawQuery("select name from sqlite_master where type='table' order by name", null);
        Cursor new_cursor = new_read_db.rawQuery("select name,sql from sqlite_master where type='table' order by name", null);
        List<String> oldTables = new ArrayList<>();
        List<String> newTables = new ArrayList<>();
        List<String> commonTables = new ArrayList<>();
        List<String> createTableSqls = new ArrayList<>();
        List<String> tables_old_alone = new ArrayList<>();
        List<String> tables_should_delete = new ArrayList<>();
        HashMap<String, Integer> oldIndexs = new HashMap<>();
        HashMap<String, Integer> newIndexs = new HashMap<>();
        try {
            if (old_cursor != null && old_cursor.getCount() > 0) {
                while (old_cursor.moveToNext()) {
                    String name = old_cursor.getString(0);//name
                    oldTables.add(name);
                }
            }
        } finally {
            if (old_cursor != null) {
                old_cursor.close();
            }
        }

        try {
            if (new_cursor != null && new_cursor.getCount() > 0) {
                while (new_cursor.moveToNext()) {
                    String name = new_cursor.getString(0);//name
                    String sql = new_cursor.getString(1);//sql
                    Log.d(TAG, "greendao sql:" + sql);
                    createTableSqls.add(sql);
                    newTables.add(name);
                }

            }
        } finally {
            if (new_cursor != null) {
                new_cursor.close();
            }
        }

        for (int i = 0; i < oldTables.size(); i++) {
            String tableName = oldTables.get(i);
            if (newTables.contains(tableName)) {
                if (tableName.equals(ANDROID_META_DATA) || tableName.equals(ANDROID_SEQUENCE)) {
                    continue;
                }
                commonTables.add(tableName);
            } else {
                tables_old_alone.add(tableName);
            }
        }

        HashMap<String, List<String>> tableName_commonColumns = new HashMap<>();
        for (int p = 0; p < commonTables.size(); p++) {
            String tablename = commonTables.get(p);
            List<String> oldColumns = getColumns(old_read_db, tablename);
            List<String> newColumns = getColumns(new_read_db, tablename);
            List<String> sameColumns = new ArrayList<>();


            if (newColumns.size() < oldColumns.size()) {
                for (int i = 0; i < newColumns.size(); i++) {
                    for (int j = 0; j < oldColumns.size(); j++) {
                        if (newColumns.get(i).equals(oldColumns.get(j))) {
                            sameColumns.add(newColumns.get(i));
                            newIndexs.put(newColumns.get(i), i);
                            oldIndexs.put(oldColumns.get(j), j);
                            break;
                        }
                    }
                }
            } else {
                for (int i = 0; i < oldColumns.size(); i++) {
                    for (int j = 0; j < newColumns.size(); j++) {
                        if (newColumns.get(j).equals(oldColumns.get(i))) {
                            sameColumns.add(newColumns.get(j));
                            newIndexs.put(newColumns.get(j), j);
                            oldIndexs.put(oldColumns.get(i), i);
                            break;
                        }
                    }
                }
            }
            tableName_commonColumns.put(tablename, sameColumns);
        }

        Log.d(TAG, "queryMessage oldIndexs:" + oldIndexs.toString());
        Log.d(TAG, "queryMessage newIndexs:" + newIndexs.toString());

        tables_should_delete.addAll(commonTables);
        tables_should_delete.addAll(tables_old_alone);
        Log.i(TAG, "queryMessage commonTables:" + commonTables);
        Log.i(TAG, "queryMessage tables_should_delete:" + tables_should_delete);

        //step 1 gernerate temp tables(just copy data from common tables)
        generateTempTables(old_write_db, commonTables);
        //step 2
        DropTables(old_write_db, tables_should_delete);
        createAllTables(old_write_db, createTableSqls);
        restoreData(tableName_commonColumns);
        renameAndDropDb();

        long end = System.currentTimeMillis();
        Log.i(TAG, "<----migrateTables end---->,use time:" + (end - start) + "ms");
    }

    private void renameAndDropDb() {
        mContext.deleteDatabase(DbUtils.DB_OLD_NAME_MESSAGE);
        mContext.deleteDatabase(getNewDbName());
        File temp_File = mContext.getDatabasePath(getOldDbName());
        String[] oldDbName = temp_File.toString().split("/");
        StringBuilder newDbName = new StringBuilder();
        for (int i = 0; i < oldDbName.length; i++) {
            if (i != 0) {
                newDbName.append("/");
            }
            if (i == (oldDbName.length - 1)) {
                newDbName.append(getNewDbName());
            } else {
                newDbName.append(oldDbName[i]);
            }
        }
        Log.d(TAG, "split result oldDbName:" + Arrays.toString(oldDbName));
        Log.d(TAG, "split result newDbName:" + newDbName.toString());
        File newDb = new File(newDbName.toString());
        temp_File.renameTo(newDb);
        mPhoneNumber = null;
        Log.d(TAG, "newDb exist ? " + newDb.exists());
    }

    private void restoreData(HashMap<String, List<String>> tableName_commonColumns) {
        Iterator iter = tableName_commonColumns.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry entry = (Map.Entry) iter.next();
            String common_tableName = (String) entry.getKey();
            List<String> commonColumns = (List<String>) entry.getValue();
            queryAndInsertData(common_tableName, commonColumns);
        }
    }

    private void queryAndInsertData(String common_tableName, List<String> commonColumns) {
        Cursor oldColumn_cursor = null;
        try {
            oldColumn_cursor = old_read_db.rawQuery("select * from " + common_tableName, null);
            if (null == oldColumn_cursor && !oldColumn_cursor.moveToFirst()) {
                Log.e(TAG,String.format("table <%s> has no data !",common_tableName));
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (oldColumn_cursor != null) {
                oldColumn_cursor.close();
            }
        }
        Log.d(TAG, "queryMessage same columns:" + commonColumns + ",count:" + commonColumns.size());

        final String querySql_columns = TextUtils.join(",", commonColumns);
        String tempTableName = common_tableName.concat("_TEMP");
        StringBuilder excuteSql = new StringBuilder();
        excuteSql.append("INSERT INTO ")
                .append(common_tableName)
                .append("(")
                .append(querySql_columns)
                .append(")")
                .append(" SELECT ")
                .append(querySql_columns)
                .append(" FROM ")
                .append(tempTableName);

        Log.i(TAG, "excuteSql:" + excuteSql.toString());
        old_write_db.execSQL(excuteSql.toString());

        Log.i(TAG,"Restore data to " + common_tableName);

        StringBuilder dropTableStringBuilder = new StringBuilder();
        dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
        old_write_db.execSQL(dropTableStringBuilder.toString());

        Log.d(TAG,"Drop temp table" + tempTableName);
    }

    private void createAllTables(SQLiteDatabase old_write_db, List<String> sqls) {
        for (String sql : sqls) {
            if (findStr(ANDROID_META_DATA, sql) || findStr(ANDROID_SEQUENCE, sql)) {
                continue;
            }
            String excuteSql = filterIntegerNotNull(sql);
            old_write_db.execSQL(excuteSql);
            Log.i(TAG, "createAllTables :" + excuteSql);
        }
    }

    private String filterIntegerNotNull(String sql) {
        return filterIntegerNotNull(sql, "INTEGER NOT NULL", "INTEGER DEFAULT -1").toString();
    }

    private boolean findStr(String src, String value) {
        Pattern p = Pattern.compile(src);
        Matcher m = p.matcher(value);
        return m.find();
    }

    private StringBuffer filterIntegerNotNull(String value, String src, String dest) {
        Pattern p = Pattern.compile(src);
        Matcher m = p.matcher(value);
        StringBuffer sb = new StringBuffer();
        boolean result = m.find();
        while (result) {//如果匹配成功就替换
            m.appendReplacement(sb, dest);
            result = m.find();//继续下一步匹配
        }
        m.appendTail(sb);
        return sb;
    }

    private void DropTables(SQLiteDatabase old_write_db, List<String> tables_should_delete) {
        for (String tableName : tables_should_delete) {
            String deleteSql = String.format("DROP TABLE IF EXISTS \"%s\"", tableName);
            old_write_db.execSQL(deleteSql);
        }
    }

    private void generateTempTables(SQLiteDatabase db, List<String> commonTables) {
        for (int i = 0; i < commonTables.size(); i++) {
            String tempTableName = "";
            try {
                String tableName = commonTables.get(i);
                tempTableName = tableName.concat("_TEMP");
                StringBuilder dropTableStringBuilder = new StringBuilder();
                dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName).append(";");
                db.execSQL(dropTableStringBuilder.toString());

                StringBuilder insertTableStringBuilder = new StringBuilder();
                insertTableStringBuilder.append("CREATE TEMPORARY TABLE ").append(tempTableName);
                insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");
                db.execSQL(insertTableStringBuilder.toString());
                Log.d(TAG, "Table】" + tableName);
                Log.i(TAG, "Generate temp table" + tempTableName);
            } catch (SQLException e) {
                Log.e(TAG, "Failed to generate temp table" + tempTableName, e);
            }
        }
    }

    public void setPhoneNumber(String phoneNumber) {
        this.mPhoneNumber = phoneNumber;
    }

    public String getPhoneNumber() {
        return this.mPhoneNumber;
    }

    public String getOldDbName() {
        return mPhoneNumber == null ? null : String.format(DbUtils.DB_OLD_NAME_MESSAGE, mPhoneNumber);
    }

    public String getNewDbName() {
        return mPhoneNumber == null ? null : String.format(DbUtils.DB_NAME_MESSAGE, mPhoneNumber);
    }

    private static List<String> getColumns(SQLiteDatabase db, String tableName) {
        List<String> columns = null;
        Cursor cursor = null;
        try {
            cursor = db.rawQuery("select * from " + tableName + " limit 0", null);
            if (null != cursor && cursor.getColumnCount() > 0) {
                columns = Arrays.asList(cursor.getColumnNames());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null)
                cursor.close();
            if (null == columns)
                columns = new ArrayList<>();
        }
        return columns;
    }
}

MigrationDbManager.java

package greenDao.db;

import android.content.Context;
import com.chinamobile.app.yuliao_common.utils.Log;
import com.chinamobile.app.yuliao_common.utils.SharePreferenceUtils;
import com.cmcc.cmrcs.android.ui.utils.GlobalConfig;
import java.util.ArrayList;
import java.util.List;
import rx.Observable;
import rx.Observer;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.schedulers.Schedulers;

public class MigrationDbManager {

    private static final String TAG = MigrationDbManager.class.getSimpleName();

    private static final MigrationDbManager instance = new MigrationDbManager();
    private List<String> db_migration;

    public static MigrationDbManager getInstance() {
        return instance;
    }

    public void startMigrate(final Context appContext) {
        String[] databaseList = appContext.databaseList();
        db_migration = new ArrayList<>(databaseList.length);
        for (String dbName : databaseList) {
            if (dbName.startsWith("rcs")) {
                char temp = dbName.charAt(4);
                if (Character.isDigit(temp) && dbName.length() == 18) {
                    db_migration.add(dbName.substring(4, 15));
                }
            }
        }
        if (db_migration.size() == 0) {
            Log.e(TAG, "No db for migrate !");
            return;
        }
        Log.i(TAG, "db for migration, userNames:" + db_migration);

        final MigrationDBHelper migrationDBHelper = MigrationDBHelper.getInstance(appContext);
        Observable.from(db_migration)
                .doOnNext(new Action1<String>() {
                    @Override
                    public void call(String phoneNumber) {
                        Log.i(TAG, "start to migrate databases in background!");
                        migrationDBHelper.updateDatabases(phoneNumber);
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(new Observer<String>() {
                    @Override
                    public void onCompleted() {
                        SharePreferenceUtils.setDBParam(appContext, GlobalConfig.APP_MIGRAION_DONE, true);
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e(TAG, "migrate onError");
                    }

                    @Override
                    public void onNext(String phoneNumber) {
                        Log.i(TAG, "migrate Completed for phoneNumber:" + phoneNumber);

                    }
                });
    }
}

参考文章:

SQLite 教程 | 菜鸟教程
Android数据库无缝升级方案
Android数据库GreenDAO3.2.2的使用(四、数据库升级)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值