美文网首页
数据库版本升级

数据库版本升级

作者: _爱笑的eyes | 来源:发表于2019-06-05 21:41 被阅读0次

    在实际开发当中,我们经常要对数据库进行升级,但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'

    }

    相关文章

      网友评论

          本文标题:数据库版本升级

          本文链接:https://www.haomeiwen.com/subject/lrcexctx.html