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

前言

本章是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 简单入门(二)多表查询和多表关联

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335