数据库版本升级

2019-06-05  本文已影响0人  _爱笑的eyes

在实际开发当中,我们经常要对数据库进行升级,但GreenDAO默认的DaoMaster.DevOpenHelper在进行数据升级时,会把旧表删除,然后创建新表,并没有迁移旧数据到新表中,从而造成数据丢失,所以使用greendao我们一般不会直接使用DaoMaster.DevOpenHelper,而是自己创建一个类继承DaoMaster.DevOpenHelper,然后重写onUpgrade方法,在这个方法中使用MigrationHelper创建一个临时表,将旧表的数据迁移到一个新表中,保证缓存数据不会丢失,当然需要用自己继承,DaoMaster.DevOpenHelper的类来创建数据库,当升级时出现实体类中字段的增删改,变量类型需要使用基本数据类型的包装类,修改或新增的字段在数据库更新后默认为null,然后删除实体类中的构造方法、get、set方法,重新make project生成,最后将build.gradle中的schemaVersion增加1即可。

使用场景:

版本升级后,增加表、删除表、增加字段、删减字段等。为了保留原始数据,需要将数据进行迁移。

操作步骤:

1.复制MigrationHelper类到项目中,它主要是通过创建一个临时表,将旧表的数据迁移到新表中

public final class MigrationHelper {

    public static boolean DEBUG = false;

    private static String TAG = "MigrationHelper";

    private static final String SQLITE_MASTER = "sqlite_master";

    private static final String SQLITE_TEMP_MASTER = "sqlite_temp_master";

    private static WeakReference<ReCreateAllTableListener> weakListener;

    public interface ReCreateAllTableListener{

        void onCreateAllTables(Database db, boolean ifNotExists);

        void onDropAllTables(Database db, boolean ifExists);

    }

    public static void migrate(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {

        printLog("【The Old Database Version】" + db.getVersion());

        Database database = new StandardDatabase(db);

        migrate(database, daoClasses);

    }

    public static void migrate(SQLiteDatabase db, ReCreateAllTableListener listener, Class<? extends AbstractDao<?, ?>>... daoClasses) {

        weakListener = new WeakReference<>(listener);

        migrate(db, daoClasses);

    }

    public static void migrate(Database database, ReCreateAllTableListener listener, Class<? extends AbstractDao<?, ?>>... daoClasses) {

        weakListener = new WeakReference<>(listener);

        migrate(database, daoClasses);

    }

    public static void migrate(Database database, Class<? extends AbstractDao<?, ?>>... daoClasses) {

        printLog("【Generate temp table】start");

        generateTempTables(database, daoClasses);

        printLog("【Generate temp table】complete");

        ReCreateAllTableListener listener = null;

        if (weakListener != null) {

            listener = weakListener.get();

        }

        if (listener != null) {

            listener.onDropAllTables(database, true);

            printLog("【Drop all table by listener】");

            listener.onCreateAllTables(database, false);

            printLog("【Create all table by listener】");

        } else {

            dropAllTables(database, true, daoClasses);

            createAllTables(database, false, daoClasses);

        }

        printLog("【Restore data】start");

        restoreData(database, daoClasses);

        printLog("【Restore data】complete");

    }

    private static void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {

        for (int i = 0; i < daoClasses.length; i++) {

            String tempTableName = null;

            DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);

            String tableName = daoConfig.tablename;

            if (!isTableExists(db, false, tableName)) {

                printLog("【New Table】" + tableName);

                continue;

            }

            try {

                tempTableName = daoConfig.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());

                printLog("【Table】" + tableName +"\n ---Columns-->"+getColumnsStr(daoConfig));

                printLog("【Generate temp table】" + tempTableName);

            } catch (SQLException e) {

                Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e);

            }

        }

    }

    private static boolean isTableExists(Database db, boolean isTemp, String tableName) {

        if (db == null || TextUtils.isEmpty(tableName)) {

            return false;

        }

        String dbName = isTemp ? SQLITE_TEMP_MASTER : SQLITE_MASTER;

        String sql = "SELECT COUNT(*) FROM `" + dbName + "` WHERE type = ? AND name = ?";

        Cursor cursor=null;

        int count = 0;

        try {

            cursor = db.rawQuery(sql, new String[]{"table", tableName});

            if (cursor == null || !cursor.moveToFirst()) {

                return false;

            }

            count = cursor.getInt(0);

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            if (cursor != null)

                cursor.close();

        }

        return count > 0;

    }

    private static String getColumnsStr(DaoConfig daoConfig) {

        if (daoConfig == null) {

            return "no columns";

        }

        StringBuilder builder = new StringBuilder();

        for (int i = 0; i < daoConfig.allColumns.length; i++) {

            builder.append(daoConfig.allColumns[i]);

            builder.append(",");

        }

        if (builder.length() > 0) {

            builder.deleteCharAt(builder.length() - 1);

        }

        return builder.toString();

    }

    private static void dropAllTables(Database db, boolean ifExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {

        reflectMethod(db, "dropTable", ifExists, daoClasses);

        printLog("【Drop all table by reflect】");

    }

    private static void createAllTables(Database db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {

        reflectMethod(db, "createTable", ifNotExists, daoClasses);

        printLog("【Create all table by reflect】");

    }

    /**

    * dao class already define the sql exec method, so just invoke it

    */

    private static void reflectMethod(Database db, String methodName, boolean isExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {

        if (daoClasses.length < 1) {

            return;

        }

        try {

            for (Class cls : daoClasses) {

                Method method = cls.getDeclaredMethod(methodName, Database.class, boolean.class);

                method.invoke(null, db, isExists);

            }

        } catch (NoSuchMethodException e) {

            e.printStackTrace();

        } catch (InvocationTargetException e) {

            e.printStackTrace();

        } catch (IllegalAccessException e) {

            e.printStackTrace();

        }

    }

    private static void restoreData(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {

        for (int i = 0; i < daoClasses.length; i++) {

            DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);

            String tableName = daoConfig.tablename;

            String tempTableName = daoConfig.tablename.concat("_TEMP");

            if (!isTableExists(db, true, tempTableName)) {

                continue;

            }

            try {

                // get all columns from tempTable, take careful to use the columns list

                List<TableInfo> newTableInfos = TableInfo.getTableInfo(db, tableName);

                List<TableInfo> tempTableInfos = TableInfo.getTableInfo(db, tempTableName);

                ArrayList<String> selectColumns = new ArrayList<>(newTableInfos.size());

                ArrayList<String> intoColumns = new ArrayList<>(newTableInfos.size());

                for (TableInfo tableInfo : tempTableInfos) {

                    if (newTableInfos.contains(tableInfo)) {

                        String column = '`' + tableInfo.name + '`';

                        intoColumns.add(column);

                        selectColumns.add(column);

                    }

                }

                // NOT NULL columns list

                for (TableInfo tableInfo : newTableInfos) {

                    if (tableInfo.notnull && !tempTableInfos.contains(tableInfo)) {

                        String column = '`' + tableInfo.name + '`';

                        intoColumns.add(column);

                        String value;

                        if (tableInfo.dfltValue != null) {

                            value = "'" + tableInfo.dfltValue + "' AS ";

                        } else {

                            value = "'' AS ";

                        }

                        selectColumns.add(value + column);

                    }

                }

                if (intoColumns.size() != 0) {

                    StringBuilder insertTableStringBuilder = new StringBuilder();

                    insertTableStringBuilder.append("REPLACE INTO `").append(tableName).append("` (");

                    insertTableStringBuilder.append(TextUtils.join(",", intoColumns));

                    insertTableStringBuilder.append(") SELECT ");

                    insertTableStringBuilder.append(TextUtils.join(",", selectColumns));

                    insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");

                    db.execSQL(insertTableStringBuilder.toString());

                    printLog("【Restore data】 to " + tableName);

                }

                StringBuilder dropTableStringBuilder = new StringBuilder();

                dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);

                db.execSQL(dropTableStringBuilder.toString());

                printLog("【Drop temp table】" + tempTableName);

            } catch (SQLException e) {

                Log.e(TAG, "【Failed to restore data from temp table 】" + tempTableName, e);

            }

        }

    }

    private static List<String> getColumns(Database 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;

    }

    private static void printLog(String info){

        if(DEBUG){

            Log.d(TAG, info);

        }

    }

    private static class TableInfo {

        int cid;

        String name;

        String type;

        boolean notnull;

        String dfltValue;

        boolean pk;

        @Override

        public boolean equals(Object o) {

            return this == o

                    || o != null

                    && getClass() == o.getClass()

                    && name.equals(((TableInfo) o).name);

        }

        @Override

        public String toString() {

            return "TableInfo{" +

                    "cid=" + cid +

                    ", name='" + name + '\'' +

                    ", type='" + type + '\'' +

                    ", notnull=" + notnull +

                    ", dfltValue='" + dfltValue + '\'' +

                    ", pk=" + pk +

                    '}';

        }

        private static List<TableInfo> getTableInfo(Database db, String tableName) {

            String sql = "PRAGMA table_info(`" + tableName + "`)";

            printLog(sql);

            Cursor cursor = db.rawQuery(sql, null);

            if (cursor == null)

                return new ArrayList<>();

            TableInfo tableInfo;

            List<TableInfo> tableInfos = new ArrayList<>();

            while (cursor.moveToNext()) {

                tableInfo = new TableInfo();

                tableInfo.cid = cursor.getInt(0);

                tableInfo.name = cursor.getString(1);

                tableInfo.type = cursor.getString(2);

                tableInfo.notnull = cursor.getInt(3) == 1;

                tableInfo.dfltValue = cursor.getString(4);

                tableInfo.pk = cursor.getInt(5) == 1;

                tableInfos.add(tableInfo);

                // printLog(tableName + ":" + tableInfo);

            }

            cursor.close();

            return tableInfos;

        }

    }

}

2.新建一个类,继承DaoMaster.DevOpenHelper,重写onUpgrade(Database db, int oldVersion, int newVersion)方法,在该方法中使用MigrationHelper进行数据库升级以及数据迁移。

public class MyOpenHelper extends DaoMaster.OpenHelper {

    public MyOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {

        super(context, name, factory);

    }

    @Override

    public void onUpgrade(Database db, int oldVersion, int newVersion) {

        //把需要管理的数据库表DAO作为最后一个参数传入到方法中

        MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {

            @Override

            public void onCreateAllTables(Database db, boolean ifNotExists) {

                DaoMaster.createAllTables(db, ifNotExists);

            }

            @Override

            public void onDropAllTables(Database db, boolean ifExists) {

                DaoMaster.dropAllTables(db, ifExists);

            }

        },  BeanDao.class);

    }

}

然后使用MyOpenHelper替代DaoMaster.DevOpenHelper来进行创建数据库等操作

MyOpenHelper myOpenHelper =new MyOpenHelper(this, "uiuiu.db", null);

DaoMaster daoMaster =new DaoMaster(myOpenHelper.getWritableDb());

daoSession = daoMaster.newSession();

3.在数据库Bean类中,添加/删除/修改字段

1)新增的字段或修改的字段,其变量类型应使用基础数据类型的包装类,如使用Integer而不是int,避免升级过程中报错。

2)根据MigrationHelper中的代码,升级后,新增的字段和修改的字段,都会默认被赋予null值。

4.添加/删除/修改字段之后删除其set/get方法,然后重新build

5.修改build.gradle中数据库的版本号schemaVersion ,递增加1即可,就是说每升级一次加1,最后运行app

greendao {

//数据库的schema版本,也可以理解为数据库版本号

    schemaVersion5

    //设置DaoMaster、DaoSession、Dao包名,也就是要放置这些类的包的全路径。

    daoPackage'com.example.myapplication.dao'

    //设置DaoMaster、DaoSession、Dao目录

    targetGenDir'src/main/java'

}

上一篇下一篇

猜你喜欢

热点阅读