美文网首页数据库
GreenDAO 3.2.2 简单入门(三)数据库升级

GreenDAO 3.2.2 简单入门(三)数据库升级

作者: 大荒里种菜 | 来源:发表于2019-05-21 16:49 被阅读0次

    前言

    本章是GreenDAO3.2.2简单入门的最后一篇,是对前面两篇文章的进一步扩展。
    GreenDAO 3.2.2 简单入门(二)多表查询和多表关联
    GreenDAO 3.2.2 简单入门(一)增删改查

    数据库升级原因和问题

    原因:

    • 已经完成的项目,要添加新的表
    • 已经完成的项目,要对以前的表添加新的列或删除旧的列

    问题:

    • 已经完成的项目,数据库中已经存在数据甚至是大量数据
    • GreenDAO 3.2.2默认的数据库升级,会将所有的表全部删除,在重建所有表
    • 导致以前已经保存的数据全部丢失

    实战

    数据库升级的思路如下:

    • 用临时表存储原有数据
      • 用临时表的目的时为了数据库升级后不丢失原有数据
      • 将原表更名后,成为对应的临时表,临时表中包含原有数据
    • 创建新表拷贝原有数据
    • 删除临时表完成升级

    新建MigrationHelper类,其代码如下:

    public class MigrationHelper {
    
        // 调用的升级方法,第二个参数表示,只要继承了AbstractDao的实体类的Dao类都可以
        @SafeVarargs
        public static void migrate(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
            // 生成临时表,将旧表更名,编程临时表
            generateTempTables(db, daoClasses);
    
            // 创建新表
            createAllTables(db, false, daoClasses);
    
            // 将临时表的数据拷贝到新表,并删除临时表
            restorData(db, daoClasses);
        }
    
        @SafeVarargs
        private static void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
            // 循环参数中的Dao类,对参数中所有Dao类进行更名操作
            for (int i = 0; i < daoClasses.length; i++) {
                // 获得DaoConfing对象,这个对象里面封装了表名
                DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
                String tableName = daoConfig.tablename;
                // 如果表不存在跳过后续的操作
                if (!checkTable(db, tableName)) {
                    continue;
                }
    
                String tempTableName = daoConfig.tablename.concat("_TEMP");// 临时表名
                StringBuilder insertTableStringBuilder = new StringBuilder();
    
                // 注意空格,拼接sql语句,将表改名变成临时表
                insertTableStringBuilder.append("alter table ")
                        .append(tableName)
                        .append(" rename to ")
                        .append(tempTableName)
                        .append(";");
                db.execSQL(insertTableStringBuilder.toString());
            }
        }
    
        private static boolean checkTable(Database db, String tableName) {
            StringBuffer query = new StringBuffer();
            query.append("select count(*) from sqlite_master where type='table' and name='")
                    .append(tableName)
                    .append("'");
            Cursor c = db.rawQuery(query.toString(), null);
    
            if (c.moveToNext()) {
                int count = c.getInt(0);
                if (count > 0) {
                    return true;
                }
                return false;
            }
    
            return false;
        }
    
        @SafeVarargs
        private static void createAllTables(Database db, boolean ifNotExists,
                                            @NotNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
            reflectMethod(db, "createTable", ifNotExists, daoClasses);
        }
    
        // 用反射的方法来获得createTable方法,创建个Dao类相对应的实体类的表
        @SafeVarargs
        private static void reflectMethod(Database db, String methodName, boolean ifNotExists,
                                          @NotNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
            if (daoClasses.length < 1) return;
    
            try {
                for (Class<?> cls : daoClasses) {
                    /* 以User实体类为例,在生成的UserDao中有一个createTable()方法。
                     * 当User类的属性发生改变时,编译后UserDao的相应代码会发生改变,比如createTable()方法。
                     * 但是,表名并没有改变,当以下代码执行成功后,也就是新表创建了。
                     * 它的表名还是USER,而与其对应的临时表名时USER_TEMP。*/
                    Method method = cls.getDeclaredMethod(methodName, Database.class, boolean.class);
                    method.invoke(null, db, ifNotExists);
                }
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    
    
        @SafeVarargs
        private static void restorData(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 (!checkTable(db, tempTableName)) continue;
    
                // 获得临时表也就是旧表中的所有列
                List<String> columns = getColums(db, tempTableName);
                // 如果旧表的列包含此新表列,将其添加到properties集合中
                ArrayList<String> properties = new ArrayList<>(columns.size());
    
                for (int j = 0; j < daoConfig.properties.length; j++) {
                    // 获得新表的列
                    String columnName = daoConfig.properties[j].columnName;
                    // 如果旧表的列包含此新表列,将其添加到properties集合中
                    if (columns.contains(columnName)) {
                        properties.add(columnName);
                    }
                }
    
                if (properties.size() > 0) {
                    // 在集合中每个字符串元素(即列名)之间用英文下的逗号相连,用于构建sql语句中的列名
                    final String columnSQL = TextUtils.join(",", properties);
                    StringBuilder insertTableStringBuilder = new StringBuilder();
    
                    // 注意空格,拼接sql语句,拷贝旧表数据到新表
                    insertTableStringBuilder.append("insert into ")
                            .append(tableName)
                            .append("(")
                            .append(columnSQL)
                            .append(") select ")
                            .append(columnSQL)
                            .append(" from ")
                            .append(tempTableName)
                            .append(";");
                    db.execSQL(insertTableStringBuilder.toString());
                }
    
                // 删除临时表
                StringBuilder dropTableStringBuilder = new StringBuilder();
                dropTableStringBuilder.append("drop table ").append(tempTableName);
                db.execSQL(dropTableStringBuilder.toString());
            }
        }
    
        // 获得表中所有的列名
        private static List<String> getColums(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 (columns == null) {
                    columns = new ArrayList<>();
                }
            }
    
            return columns;
        }
    
    }
    

    以User类对应的USER表为例,为其新增一列SEX。第一步,为USER表准备数据:

    // 在MainActivity中onCreate()方法中调用,要注掉其他方法
    userDao.deleteAll();
    insertMany();
    

    编译运行后,更改数据库版本号(比之前的版本号高)以及User类并make project,其代码如下:

    @Entity(
            nameInDb = "USERS", // 表名
            indexes = {
                    @Index(value = "name DESC"), // 为属性name设置索引
            }
    )
    public class User {
        @Id(autoincrement = true) // 主键,要求是Long型
        private Long id;
    
        private Long cardId;
    
        @ToOne(joinProperty = "cardId") // 设置一对一关联,连接属性是cardId
        private Card card;
    
        @ToMany(referencedJoinProperty = "userId") // 设置一对多关联,连接属性是Orders类的外键userId
        private List<Orders> orders;
    
        @Index(name = "usercode_index", unique = true) // 设置索引且是唯一索引
        private String usercode;
    
        @Property(nameInDb = "username") // 设置该属性对应的列名
        @NotNull                         // 非空
        private String name;
    
        private String userAddress; // 可以为空
    
        // 为数据库升级测试用
        private String sex;
    
        @Transient // 临时存储
        private int tempUserSign;
    }
    

    在MainActivity中添加如下方法,然后将onCreate()中其他方法注掉并调用它们:

        private void migrationTest() {
            User user1 = userDao.queryBuilder().where(UserDao.Properties.Name.eq("孙悟空")).build().unique();
            user1.setSex("男");
            userDao.update(user1);
        }
    
        private void migrationQueryList() {
            String result = "显示结果为:";
            List<User> users = userDao.loadAll();
            int i = 0;
            for (User user : users) {
                i++;
                String res = result + "i=" + i + ",id:" + user.getId() + ",name:" + user.getName() +
                        ",address:" + user.getUserAddress() +
                        ",sex:" + user.getSex();
                Log.d("TAG", res);
            }
        }
    
        // 依次调用
        migrationTest();
        migrationQueryList();
    

    最后将HMROpenHelper类更改如下,就可以运行测试了。

    public class HMROpenHelper extends DaoMaster.OpenHelper{
        public HMROpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
            super(context, name, factory);
        }
    
        @Override
        public void onUpgrade(Database db, int oldVersion, int newVersion) {
            Log.d("TAG","更新了");
    //        DaoMaster.dropAllTables(db, true);
    //        onCreate(db);
    
            // 目前用USER表来测试
            MigrationHelper.migrate(db, UserDao.class);
        }
    }
    

    总结

    GreenDAO 3.2.2的简单入门就讲到这里了。

    最终源码
    GreenDAO 3.2.2 简单入门(一)增删改查
    GreenDAO 3.2.2 简单入门(二)多表查询和多表关联

    相关文章

      网友评论

        本文标题:GreenDAO 3.2.2 简单入门(三)数据库升级

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