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>... daoClasses) {
        // 生成临时表,将旧表更名,编程临时表
        generateTempTables(db, daoClasses);

        // 创建新表
        createAllTables(db, false, daoClasses);

        // 将临时表的数据拷贝到新表,并删除临时表
        restorData(db, daoClasses);
    }

    @SafeVarargs
    private static void generateTempTables(Database db, Class>... 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>... daoClasses) {
        reflectMethod(db, "createTable", ifNotExists, daoClasses);
    }

    // 用反射的方法来获得createTable方法,创建个Dao类相对应的实体类的表
    @SafeVarargs
    private static void reflectMethod(Database db, String methodName, boolean ifNotExists,
                                      @NotNull Class>... 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>... 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 columns = getColums(db, tempTableName);
            // 如果旧表的列包含此新表列,将其添加到properties集合中
            ArrayList 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 getColums(Database db, String tableName) {
        List 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;

    @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 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 简单入门(三)数据库升级)