【问题标题】:greenDao Schema UpgradegreenDao 架构升级
【发布时间】:2019-03-01 01:13:51
【问题描述】:

我看到另一个关于使用 green dao (here) 进行架构升级/迁移的问题

该答案中有很多链接可以在进行架构升级时使用一个好的模式 - 但是没有示例说明您实际对数据执行了哪些操作以正确迁移它,而且我找不到任何东西。

就我而言,我的迁移非常简单 - 我不希望转换任何现有数据,我只需要向我的架构中添加一些新表,我怀疑这是一种相当常见的情况。

在不删除用户已经保存的数据的情况下,向架构中添加新表的最简单方法是什么?一个具体的例子将不胜感激。

如果 greenDao 提供了一个类似于 DevOpenHelper 的类,该类将简单地添加以前不存在于架构中的新表/列,而不首先删除现有的表/数据,那就太棒了。

【问题讨论】:

    标签: android orm greendao


    【解决方案1】:

    我终于有时间自己深入研究这个问题,并意识到在旧表中保留数据的同时添加新表非常容易。

    免责声明:虽然我意识到此实现特定于我的场景,但我认为它对像我这样专门使用 Android ORM 工具 (greenDao) 来处理 Android 上的 SQLite 的人很有帮助。我知道这对于那些从一开始就编写自己的表创建查询的人来说是很常见的,但是对于那些没有在 Android 上使用 SQLite DB 的胆量的人来说,我认为这个示例会有所帮助。

    回答: 您可以修改 DevOpenHelper 内部类或创建自己的类。我暂时选择编辑 DevOpenHelper 以保持我的示例简单 - 但是请注意,如果您重新生成 greendao 类,DevOpenHelper 将被覆盖。最好创建自己的类,例如“MyOpenHelper”并使用它。

    在我进行更改之前,DevOpenHelper.onUpgrade 看起来像这样:

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 
    {
            Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
            dropAllTables(db, true);
            onCreate(db);
    }
    

    不要删除所有表,而是看一下 GreenDao 自动生成的 createAllTables 方法。

    重写 onUpgrade 以检查“oldVersion”是否是您要升级的版本,然后只为“新”表调用 createTable 方法。这是我的 onUpgrade 方法现在的样子:

    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 
    {
            Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + 
    
            //Going from older schema to new schema
            if(oldVersion == 3 && newVersion == 4)
            {
                boolean ifNotExists = false;
    
                //Leave old tables alone and only create ones that didn't exist
                //in the previous schema
                NewTable1Dao.createTable(db, ifNotExists);
                NewTable2Dao.createTable(db, ifNotExists);
                NewTable3Dao.createTable(db, ifNotExists);
                NewTable4Dao.createTable(db, ifNotExists);
            }
            else
            {
                dropAllTables(db, true);
                onCreate(db);
            }
    }
    

    添加新列与此类似,但您必须编写一些 SQL 或查看 greenDao 自动生成的 SQL 创建语句并利用这些语句。

    要将单个新列(NEW_COLUMN,假设它是 INTEGER 类型)添加到现有表 (EXISTING_TABLE),请执行以下操作:

    db.execSQL("ALTER TABLE 'EXISTING_TABLE' ADD 'NEW_COLUMN' INTEGER");
    

    现在对我来说,我需要做的就是添加新表格,所以这最终变得相当简单。希望其他人觉得这很有用。

    【讨论】:

    • 我们在哪里设置新版本号?
    • 但是如何恢复以前的数据呢?我想没有代码可以保存它吗?
    【解决方案2】:

    我采用了一种稍微不同的方法来自动处理更新,无论之前的用户来自哪里。 首先,我创建了一个在 SQLDatabase 上实现 onUpgrade 方法的类

    public abstract class AbstractMigratorHelper {
    
    public abstract void onUpgrade(SQLiteDatabase db);
    }
    

    从这个类将继承我之后声明的所有迁移器助手

    我会写一个例子

    public class DBMigrationHelper5 extends AbstractMigratorHelper {
    
    /* Upgrade from DB schema x to schema x+1 */
    
    
    public void onUpgrade(SQLiteDatabase db) {
        //Example sql statement
        db.execSQL("ALTER TABLE user ADD COLUMN USERNAME TEXT");
     }
    }
    

    在此之后,您需要在升级时实际调用的类上实现逻辑,您需要删除以前的 DevOpenHelper 以获得可能看起来像这样的自定义类

    public static class UpgradeHelper extends OpenHelper {
    
        public UpgradeHelper(Context context, String name, CursorFactory factory) {
            super(context, name, factory);
        }
    
        /**
         * Here is where the calls to upgrade are executed
         */
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    
            /* i represent the version where the user is now and the class named with this number implies that is upgrading from i to i++ schema */
            for (int i = oldVersion; i < newVersion; i++) {
                try {
                    /* New instance of the class that migrates from i version to i++ version named DBMigratorHelper{version that the db has on this moment} */
                    AbstractMigratorHelper migratorHelper = (AbstractMigratorHelper) Class.forName("com.nameofyourpackage.persistence.MigrationHelpers.DBMigrationHelper" + i).newInstance();
    
                    if (migratorHelper != null) {
    
                        /* Upgrade de db */
                        migratorHelper.onUpgrade(db);
                    }
    
                } catch (ClassNotFoundException | ClassCastException | IllegalAccessException | InstantiationException e) {
    
                    Log.e(TAG, "Could not migrate from schema from schema: " + i + " to " + i++);
                    /* If something fail prevent the DB to be updated to future version if the previous version has not been upgraded successfully */
                    break;
                }
    
    
            }
        }
    }
    

    因此,如果您仔细命名迁移助手(即 MigrationHelper5 执行从模式 5 到模式 6 的迁移),您可以实现此逻辑,然后在每个 MigratorHelper 类中使用您需要的所有 sql 代码实现 execSQL 调用实施。

    最后再说一点,如果你使用 proguard,那么 find name by class 方法可能不起作用,因为在混淆代码时会更改类名。您可能需要考虑在 proguard 配置文件 (proguard-rules.pro) 上添加一个例外,以排除从 AbstractMigratorHelper 扩展的任何类

    # Avoid errors when upgrading database migrators
    
    -keep public class * extends yourpackage.locationofyourclass.AbstractMigratorHelper
    

    【讨论】:

    • 抱歉唤醒了这么老的帖子,但我想知道:对于迁移类,使用“MyEntity.TABLENAME”创建 SQL 请求(例如“CREATE TABLE”)更好还是可以我们安全地将整个请求创建为“字符串”?我之所以问,是因为我看到很多人在升级表时直接从实体中获取列名,并且想知道最好的方法是什么。
    【解决方案3】:

    我的做法略有不同。

    我将新的@DatabaseTable 类和任何@DatabaseFields 添加到现有的@DatabaseTable 类并运行DatabaseConfigUtil。

    然后我将向我的 DatabaseUpgrader 类添加一个新方法并修改我的 DatabaseHelper,更改 DATABASE_VERSION 值和 onUpdate 方法

    public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
    
        private static final int DATABASE_VERSION = 3;
    
        @Override
        public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
    
            if (newVersion > oldVersion) {
                switch (oldVersion) {
                    case 1:
                        DatabaseUpdater.from1to2(connectionSource);
                        DatabaseUpdater.from2to3(connectionSource);
                        break;
    
                    case 2:
                        DatabaseUpdater.from2to3(connectionSource);
                        break;
    
                    default:
                        onCreate(db);
                }
            }
        }
    
        public static DatabaseHelper getInstance() {
    
            return DatabaseHelper.mHelper;
        }
    
        public static void setInstance(Context context) {
    
            DatabaseHelper.mHelper = new DatabaseHelper(context);
        }
    
        …
    }
    

    然后在 DatabaseUpdater 类中

    public class DatabaseUpdater {
    
        private static final String TAG = "DatabaseHelper";
    
        public static void from1to2(ConnectionSource connectionSource) {
    
            try {
                DatabaseHelper helper = DatabaseHelper.getInstance();
    
                //Example add a table
                TableUtils.createTable(connectionSource, AnotherEntity.class);
    
    
            } catch (SQLException e) {
                Log.e(TAG, "Error upgrading database to v2: ", e);
            } catch (java.sql.SQLException e) {
                e.printStackTrace();
            }
    
        }
    
        public static void from2to3(ConnectionSource connectionSource) {
    
            try {
                DatabaseHelper helper = DatabaseHelper.getInstance();
    
                //Example add a field to a table
                RuntimeExceptionDao<MyEntity, Integer> myDao = helper.getMyDao();
                diaryDao.executeRaw("ALTER TABLE myEntity ADD firstNewField");
                diaryDao.executeRaw("ALTER TABLE myEntity ADD anotherNewField");
    
    
            } catch (SQLException e) {
                Log.e(TAG, "Error upgrading database to v3: ", e);
            }
    
        }
    }
    

    【讨论】:

    • 很棒的东西@RobCroll,我喜欢我的方法是,无论你有多少迁移,你都不必更改升级方法的代码。您只需要与类的名称保持一致,这对我来说并不难,因为通常我会复制现有的 Migration 类来创建新的类。你的好处是你只有一个用于所有迁移的课程,但如果你做很多迁移,课程可能会很长
    【解决方案4】:

    回答@MBH 在第一个答案时发布的问题。我也没有在这篇文章中找到答案,因此添加了。

    GreenDAO 使用 build.gradle 文件中的架构版本号。 Gradle 文件应包含以下内容

    android {
       ...
    }
    greendao {
       schemaVersion 1
    }
    

    请参阅此link 了解更多信息。然后在升级时将此数字更改为 2 或任何增量。根据这个数字,GreenDAO 从 android.database.sqlite.SQLiteDatabase.DatabaseOpenHelper.java 调用下面的 API

    public DatabaseOpenHelper(Context context, String name, int version)
    

    作为 Sqlite DB 升级的标准方法,它调用下面的 API

    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
    

    正如其他答案所暗示的,此方法可以在派生类中被覆盖,并且可以处理任何项目特定的升级。 希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-08
      • 2015-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多