Android数据库自动升级

Android数据库自动升级

本文所有的数据库操作都基于ormlite框架,不了解的可以先自行学习。


传统升级方案的讨论

在以往的数据库升级方案中,大家基本的做法都是重写 onUpgrade() 方法,通过 oldVersion 与 newVersion 来判断数据库的升级策略,然后使用sql语句对表进行操作。这种方法很适合在小型项目进行开发,但是当开发大型的项目,由于数据表增多,项目迭代频繁,人员技术相差较大等各种原因,会出现很多问题。

现在我们讨论另一种数据库升级方案,当数据库版本号变化时,通过比较原始的数据表结果与当前的数据表结构是否有变化,来判断数据库是否升级,并通过差异化判断来决定升级过程,达到自动升级的目的。


自动升级方案设计

获取数据表结构

  • 获取原始的数据表建表语句

SQLite 数据库中有一个特殊的名叫 sqlite_master 的数据表,每条CREATE INDEX语句的文本储存于sqlite_master或sqlite_temp_master表中,我们可以通过下面的代码获取到原始建表语句。

// 获取数据库已有表的建表语句
cursor = db.rawQuery("select * from sqlite_master where type = ? AND name = ?",
                    new String[]{"table", tableName});
if (cursor != null) {
    cursor.moveToFirst();
    int columnIndex = cursor.getColumnIndex("sql");
        if (-1 != columnIndex && cursor.getCount() > 0) {
            String oldCreateSql= cursor.getString(columnIndex);              
        }
}
  • 获取当前数据表的建表语句就简单了,直接调用ormlite的接口即可。
// 获取当前数据表建表语句
String newCreateSql= TableUtils.getCreateTableStatements(connectionSource, clazz).get(0); 

转换表结构

现在我们已经有了升级前后的建表语句,那么怎么比较建表语句的差异呢?首先,我们定义一个数据表列的结构,用来结构化表的建表结构。

/**
 * 数据库表中列的结构
 */
public class ColumnStruct {

    private String columnName; // 列名
    private String columnLimit; // 列的限制条件

    public ColumnStruct() {
    }

    public ColumnStruct(String columnName, String columnLimit) {
        this.columnName = columnName;
        this.columnLimit = columnLimit;
    }

    public String getColumnName() {
        return columnName;
    }

    public void setColumnName(String columnName) {
        this.columnName = columnName;
    }

    public String getColumnLimit() {
        return columnLimit;
    }

    public void setColumnLimit(String columnLimit) {
        this.columnLimit = columnLimit;
    }
}

通过这个方法,我们可以对表结构进行拆分,得到表的列结构。这个方法有点笨的感觉,有好的想法欢迎轻喷。

/**
* 生成表结构
* @param tableStruct 规范建表语句
* @return
*/
public static List<ColumnStruct> getColumnStruct(String tableStruct) {
    // 这个函数总是给人一种不爽的感觉,欢迎提出更优秀的方案
    List<ColumnStruct> columnStructList = new ArrayList<>();
    // 解析过程根据标准的ormlite建表语句设计
    String subString = tableStruct.substring(tableStruct.indexOf("(") + 1, tableStruct.length() - 1);
    String[] sub = subString.split(", ");
    for (String str : sub) {
        if (str.contains("(") || str.contains(")")) {
            str = str.replace("(", "").replace(")", "");
        }
        str = removeUnlessSpace(str);
        if (str.startsWith("`")) {
            String[] column = str.split("` ");
            columnStructList.add(new ColumnStruct(column[0].replace("`", ""), column[1]));
        } else {
            // 附加的额外字段限制,不是以`开始
            if (str.contains(",")) {
                String[] column = str.split(" `");
                String[] columns = column[1].replace("`", "").split(",");
                for (String str1 : columns) {
                    modifyColumnStruct(columnStructList, str1, "UniqueCombo");
                }
            } else {
                String[] column = str.split(" `");
                modifyColumnStruct(columnStructList, column[1].replace("`", ""), column[0]);
            }
        }
    }
    return columnStructList;
}

数据库中表的几种情况

有了表的升级前后的列结构就好办了,数据库表的变化存在这几种情况:

  • 新增表。数据表原始的列结构为空时,此时即为新增表;

  • 删除表。当数据表新的别结构为空时,此时即为删除表;

  • 修改表。表的修改存在几种情况:1.增加列;2.删除列;3.修改列的限制条件。在这几种情况下我们进行优先级的排序,当限制条件被改变时,这是最麻烦的情况,简单做法:删除旧表重建新表(能力有限一直没想到好的方案)。如果原始列的限制条件没有被改变,此时我们就可以进行数据库的升级了。

第一步,我们需要得到哪些列是保留不变的。

    /**
     * 获得没有变化的列
     */
    private String getCopyColumns(List<String> oldColumns, List<String> deleteList) {
        StringBuilder columns = new StringBuilder("");
        if (oldColumns == null || deleteList == null) {
            return columns.toString();
        }
        int index = 0;
        // 存在删除集合里的列不添加
        for (String columnName : oldColumns) {
            if (!CollectionUtil.existValue(columnName, deleteList)) {
                if (index == 0) {
                    columns.append("`").append(columnName).append("`");
                } else {
                    columns.append(", ").append("`").append(columnName).append("`");
                }
                index++;
            }
        }
        return columns.toString();
    }

第二步,进行数据表的更新,在这里我们使用的是先新建一个临时表,然后对数据进行拷贝。

    /**
     * 拷贝数据的方式更新
     *
     * @param columns 原始列减去删除的列
     */
    private void upgradeByCopy(SQLiteDatabase db, ConnectionSource cs, String columns) throws SQLException {
        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) {
                Timber.e("", e);
                TableUtils.createTable(cs, clazz);
            }
            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) {
            throw new SQLException("update fail");
        } finally {
            db.endTransaction();
        }
    }

升级方案

首先,我们只需要新建一个DatabaseHelper,继承我们的 OrmLiteDatabaseHelper,OrmLiteDatabaseHelper 封装了数据库升级的策略。

DatabaseHelper

public class DatabaseHelper extends OrmLiteDatabaseHelper {

    /**
     * 数据库名称
     */
    private static final String DATABASE_NAME = "mydatabase.db";

    /**
     * 数据库版本号
     */
    private static final int DATABASE_VERSION = 4;

    private static DatabaseHelper instance;

    private DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        addTable();
    }

    public static DatabaseHelper getInstance(Context context) {
        if (instance == null) {
            synchronized (DatabaseHelper.class) {
                if (instance == null) {
                    instance = new DatabaseHelper(context);
                }
            }
        }
        return instance;
    }

    /**
     * 注册数据表
     */
    private void addTable() {
        registerTable(City.class);
    }
}

DatabaseHelper 通过调用父类的 registerTable() 方法进行表的注册。当数据库需要升级时,只需要增加数据库版本号DATABASE_VERSION 的值即可。

    /**
     * 注册数据表
     *
     * @param clazz 表的列结构bean
     * @param <T>
     */
    public <T> void registerTable(Class<T> clazz) {
        if (tableHandlers == null) {
            tableHandlers = new ArrayList<>();
        }
        DatabaseHandler handler = new DatabaseHandler<>(clazz);
        if (isValid(handler, tableHandlers)) {
            tableHandlers.add(handler);
        }
    }

详细的实现流程可查看源码。


源码下载

示例源码下载:
https://github.com/wobuaihuangjun/DatabaseManager


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值