背景介绍
公司要对一个运营了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的使用(四、数据库升级)