realm - 迁移时手动删除 Realm

标签 realm realm-java

Realm 平台:移动 Android
Realm 版本:3.3.2
加密:

我有一个生产应用程序。当我的应用程序 v0.3.0 到 v0.14.0 需要迁移时,我使用自动删除 Realm 。由于某些原因,在 v0.14.2 上我必须手动迁移。一些用户收到错误,因为他们从 v0.14.0 下的版本更新,而我只处理从 v0.14.0 到 v0.14.2 的迁移。我很困惑是否从一开始就处理版本,因为我必须进行大量迁移。因此,如果用户从 v0.14.0 以下的版本更新我的应用程序,我想在迁移中手动删除 Realm 。如何做到这一点?

我确实喜欢这个,但我仍然遇到RealmMigrationNeededException

@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
    RealmSchema schema = realm.getSchema();


    if (oldVersion == 0) {
        //update from v0.10.5 / update from v0.13.0
        if (schema.get("User").hasField("blocks") || !schema.get("Contact").hasField("pinned")) {
            realm.deleteAll();
            return;
        }

        addField(schema, "Message", "messageDuration", int.class);
        addField(schema, "Message", "starred", boolean.class);
        oldVersion++;
    }

    if (oldVersion == 1) {
        if (!schema.contains("PhoneBook")) {
            schema.create("PhoneBook")
                    .addField("phone", String.class)
                    .addPrimaryKey("phone")
                    .addField("name", String.class)
                    .addField("image", String.class);
        }

        if (schema.contains("RealmString")) {
            schema.remove("RealmString");
        }
        oldVersion++;
    }
}

最佳答案

这是一种实验方法:

如果您使用以下 Proguard

-keepnames public class * extends io.realm.RealmModel
-keep public class * extends io.realm.RealmModel { *; }
-keepnames public class * extends io.realm.RealmObject
-keep public class * extends io.realm.RealmObject { *; }
-keepattributes *Annotation*

然后使用以下代码

public class AutoMigration
        implements RealmMigration {
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface MigrationIgnore {
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface MigratedField {
        FieldAttribute[] fieldAttributes();
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface MigratedLink {
        Class<? extends RealmModel> linkType(); // RealmList<T extends RealmModel> is nice, but T cannot be obtained through reflection.
    }

    @Override
    public int hashCode() {
        return AutoMigration.class.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return obj != null && obj instanceof AutoMigration;
    }

    @Override
    public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
        RealmConfiguration realmConfiguration = realm.getConfiguration();

        Set<Class<? extends RealmModel>> latestRealmObjectClasses = realmConfiguration.getRealmObjectClasses();
        RealmSchema realmSchema = realm.getSchema();
        Set<RealmObjectSchema> initialObjectSchemas = realmSchema.getAll();

        // first we must create any object schema that belongs to model class that is not part of the schema yet, to allow links.
        List<RealmObjectSchema> createdObjectSchemas = new LinkedList<>();

        // first we must check for classes that are in the schema, but are not in the configuration.
        Set<String> modelClassNames = new LinkedHashSet<>();
        Map<String, Class<? extends RealmModel>> modelClassNameToClassMap = new LinkedHashMap<>();
        Set<String> schemaClassNames = new LinkedHashSet<>();
        Map<String, RealmObjectSchema> schemaClassNameToObjectSchemaMap = new LinkedHashMap<>();
        for(Class<? extends RealmModel> modelClass : latestRealmObjectClasses) {
            modelClassNames.add(modelClass.getSimpleName()); // "Cat", requires `-keepnames public class * extends io.realm.RealmObject`
            modelClassNameToClassMap.put(modelClass.getSimpleName(), modelClass);
        }
        for(RealmObjectSchema objectSchema : initialObjectSchemas) {
            schemaClassNames.add(objectSchema.getClassName()); // "Cat", requires `-keepnames public class * extends io.realm.RealmObject`
            schemaClassNameToObjectSchemaMap.put(objectSchema.getClassName(), objectSchema);
        }

        // now we must check if the model contains classes that are not part of the schema.
        for(String modelClassName : modelClassNames) {
            if(!schemaClassNames.contains(modelClassName)) {
                // the model class is not part of the schema, we must add it to the schema.
                RealmObjectSchema objectSchema = realmSchema.create(modelClassName);
                createdObjectSchemas.add(objectSchema);
            }
        }

        // we must check if existing schema classes have changed fields, or if they were removed from the model.
        for(String objectClassName : schemaClassNames) {
            RealmObjectSchema objectSchema = schemaClassNameToObjectSchemaMap.get(objectClassName);
            if(modelClassNames.contains(objectClassName)) {
                // the model was found in the schema, we must match their fields.
                Class<? extends RealmModel> modelClass = modelClassNameToClassMap.get(objectClassName);
                matchFields(realmSchema, objectSchema, modelClass);
            }
        }
        // now that we've set up our classes, we must also match the fields of newly created schema classes.
        for(RealmObjectSchema createdObjectSchema : createdObjectSchemas) {
            Class<? extends RealmModel> modelClass = modelClassNameToClassMap.get(createdObjectSchema.getClassName());
            matchFields(realmSchema, createdObjectSchema, modelClass);
        }

        // it is now safe to remove classes if they were removed from the model.
        for(String objectClassName : schemaClassNames) {
            RealmObjectSchema objectSchema = schemaClassNameToObjectSchemaMap.get(objectClassName);
            if(!modelClassNames.contains(objectClassName)) {
                // the model class was not part of the schema, so we must remove the object schema.
                realmSchema.remove(objectClassName);
            }
        }
    }

    private void matchFields(RealmSchema realmSchema, RealmObjectSchema objectSchema, Class<? extends RealmModel> modelClass) {
        Field[] allModelFields = modelClass.getDeclaredFields();
        Set<String> modelFieldNames = new LinkedHashSet<>(allModelFields.length);
        Map<String, Field> modelFieldNameToFieldMap = new LinkedHashMap<>(allModelFields.length);
        for(Field field : allModelFields) {
            modelFieldNames.add(field.getName());
            modelFieldNameToFieldMap.put(field.getName(), field);
        }
        Set<String> schemaFieldNames = objectSchema.getFieldNames(); // field names require `-keep public class * extends io.realm.RealmObject { *; }`
        for(String schemaFieldName : schemaFieldNames) {
            if(!modelFieldNames.contains(schemaFieldName)) {
                // the model does not contain this field, so it no longer exists. We must remove this field.
                objectSchema.removeField(schemaFieldName);
            }
        }
        for(String modelFieldName : modelFieldNames) {
            Field field = modelFieldNameToFieldMap.get(modelFieldName);
            if(Modifier.isStatic(field.getModifiers())) { // we must ignore static fields!
                continue;
            }
            if(Modifier.isTransient(field.getModifiers())) { // transient fields are ignored.
                continue;
            }
            if(field.isAnnotationPresent(MigrationIgnore.class)) {
                continue; // manual ignore.
            }
            Class<?> fieldType = field.getType();
            if(!schemaFieldNames.contains(modelFieldName)) {
                // the schema does not contain the model's field, we must add this according to type!
                if(isNonNullPrimitive(fieldType) || isPrimitiveObjectWrapper(fieldType) || isFieldRegularObjectType(fieldType)) {
                    objectSchema.addField(modelFieldName, fieldType);
                } else {
                    if(fieldType == RealmResults.class) { // computed field (like @LinkingObjects), so this should be ignored.
                        //noinspection UnnecessaryContinue
                        continue;
                    } else if(fieldType == RealmList.class) {
                        // TODO: value lists in 4.0.0!
                        MigratedLink migratedLink = field.getAnnotation(MigratedLink.class);
                        if(migratedLink == null) {
                            throw new IllegalStateException("Link list [" + field.getName() + "] cannot be added to the schema without @MigratedLink(linkType) annotation.");
                        }
                        Class<? extends RealmModel> linkObjectClass = migratedLink.linkType();
                        String linkedObjectName = linkObjectClass.getSimpleName();
                        RealmObjectSchema linkedObjectSchema = realmSchema.get(linkedObjectName);
                        if(linkedObjectSchema == null) {
                            throw new IllegalStateException("The object schema [" + linkedObjectName + "] defined by link [" + modelFieldName + "] was not found in the schema!");
                        }
                        objectSchema.addRealmListField(field.getName(), linkedObjectSchema);
                    } else {
                        if(!RealmModel.class.isAssignableFrom(fieldType)) {
                            continue; // this is most likely an @Ignore field, let's just ignore it
                        }
                        String linkedObjectName = field.getType().getSimpleName();
                        RealmObjectSchema linkedObjectSchema = realmSchema.get(linkedObjectName);
                        if(linkedObjectSchema == null) {
                            throw new IllegalStateException("The object schema [" + linkedObjectName + "] defined by field [" + modelFieldName + "] was not found in the schema!");
                        }
                        objectSchema.addRealmObjectField(field.getName(), linkedObjectSchema);
                    }
                }
            }
            // even if it's added, its attributes might be mismatched! This must happen both if newly added, or if already exists.
            if(isNonNullPrimitive(fieldType) || isPrimitiveObjectWrapper(fieldType) || isFieldRegularObjectType(fieldType)) {
                matchMigratedField(objectSchema, modelFieldName, field);
            }
        }
    }

    private void matchMigratedField(RealmObjectSchema objectSchema, String modelFieldName, Field field) {
        MigratedField migratedField = field.getAnnotation(MigratedField.class); // @Required is not kept alive by its RetentionPolicy. We must use our own!
        if(migratedField != null) {
            boolean isIndexed = false;
            boolean isRequired = false;
            boolean isPrimaryKey = false;
            for(FieldAttribute fieldAttribute : migratedField.fieldAttributes()) {
                if(fieldAttribute == FieldAttribute.INDEXED) {
                    isIndexed = true;
                } else if(fieldAttribute == FieldAttribute.REQUIRED) {
                    isRequired = true;
                } else if(fieldAttribute == FieldAttribute.PRIMARY_KEY) {
                    isPrimaryKey = true;
                }
            }
            if(isPrimaryKey && !objectSchema.isPrimaryKey(modelFieldName)) {
                if(objectSchema.hasPrimaryKey()) {
                    throw new UnsupportedOperationException(
                            "Multiple primary keys are not supported: [" + objectSchema
                                    .getClassName() + " :: " + modelFieldName + "]");
                }
                objectSchema.addPrimaryKey(modelFieldName);
            }
            if(!isPrimaryKey && objectSchema.isPrimaryKey(modelFieldName)) {
                objectSchema.removePrimaryKey();
            }
            // index management must be after primary key because removePrimaryKey() removes index as well.
            if((isIndexed || isPrimaryKey) && !objectSchema.hasIndex(modelFieldName)) {
                objectSchema.addIndex(modelFieldName);
            }
            if(!isIndexed && !isPrimaryKey /* primary key is indexed by default! */ && objectSchema.hasIndex(modelFieldName)) {
                objectSchema.removeIndex(modelFieldName);
            }
            if(isNonNullPrimitive(field.getType())) {
                if(!objectSchema.isRequired(modelFieldName)) {
                    objectSchema.setNullable(modelFieldName, false);
                }
            } else {
                if(isRequired && objectSchema.isNullable(modelFieldName)) {
                    objectSchema.setNullable(modelFieldName, false);
                }
                if(!isRequired && !objectSchema.isNullable(modelFieldName)) {
                    objectSchema.setNullable(modelFieldName, true);
                }
            }
        }
    }

    private boolean isFieldRegularObjectType(Class<?> fieldType) {
        return fieldType == String.class || fieldType == Date.class || fieldType == byte[].class;
    }

    private boolean isPrimitiveObjectWrapper(Class<?> fieldType) {
        return fieldType == Boolean.class //
                || fieldType == Byte.class || fieldType == Short.class || fieldType == Integer.class || fieldType == Long.class //
                || fieldType == Float.class || fieldType == Double.class;
    }

    private boolean isNonNullPrimitive(Class<?> fieldType) {
        return fieldType == boolean.class //
                || fieldType == byte.class || fieldType == short.class || fieldType == int.class || fieldType == long.class //
                || fieldType == float.class || fieldType == double.class;
    }
}

然后,当模型类具有 @PrimaryKey@Ignore@Required 时,您可以对字段进行注释@Index 适用于所有当前模型类

@Index
@AutoMigration.MigratedField(fieldAttributes = {FieldAttribute.INDEXED})
private String name;

@Required
@AutoMigration.MigratedField(fieldAttributes = {FieldAttribute.REQUIRED})
private String ownerName;

private Cat cat;

@AutoMigration.MigratedLink(linkType = Cat.class)
private RealmList<Cat> manyCats;

然后你可以执行以下代码

if (oldVersion == 0) {
    //update from v0.10.5 / update from v0.13.0
    // if (schema.get("User").hasField("blocks") || !schema.get("Contact").hasField("pinned")) {
    //    realm.deleteAll();
    //    return;
    //}

    //addField(schema, "Message", "messageDuration", int.class);
    //addField(schema, "Message", "starred", boolean.class);
    //oldVersion++;
    AutoMigration autoMigration = new AutoMigration();
    autoMigration.migrate(realm, oldVersion, newVersion);
    return; // <-- !! all fields of current model class version will be added !!
}
if(oldVersion == 1) {
    // manual migration
}

请在真实的数据库中尝试一下,该代码稍有实验性。


其他选项是首先使用动态 Realm 打开 Realm,检查其架构,如果太旧,则删除该 Realm,然后使用指定的迁移打开它。

DynamicRealm dynRealm = DynamicRealm.getInstance(config);
if(/* check schema like in migration*/) {
    dynRealm.close();
    Realm.delete(config);
}
if(!dynRealm.isClosed()) {
    dynRealm.close();
}
Realm realm = Realm.getInstance(config);

关于realm - 迁移时手动删除 Realm ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46682384/

相关文章:

xcode - Realm 错误 : Migration

android - OkBuck 和 Realm 的问题

realm - 无法删除 Realm 用户

android - 如何在删除其文件之前关闭() Realm 的所有实例

android - 在 RealmMigration 中创建对象并将其添加到 RealmList

ios - 排序快速插入的 Realm 记录

java - 更新到 Realm 0.89.0(从 0.87.2)

swift - 无法将 Appdata 类型的值转换为预期参数类型 Object.type Realm Swift

java - Realm createOrUpdateAllFromJson 创建 RealmModel 对象而不持久化?

Android - 将领域列表保存在领域对象内