Java 在所有Firestore文档上添加新字段或更改结构

Java 在所有Firestore文档上添加新字段或更改结构,java,android,database,google-cloud-firestore,Java,Android,Database,Google Cloud Firestore,考虑一组用户。集合中的每个文档都有名称和电子邮件作为字段 { "users": { "uid1": { "name": "Alex Saveau", "email": "saveau.alexandre@gmail.com" }, "uid2": { ... }, "uid3": { ... } } } 现在考虑一下,使用这个工作的CloudFireStore数据库结构,我启动了我的第一个移动应用程序版本。然后,在某个时刻,我意识

考虑一组
用户
。集合中的每个文档都有
名称
电子邮件
作为字段

{
  "users": {
    "uid1": {
      "name": "Alex Saveau",
      "email": "saveau.alexandre@gmail.com"
    },
    "uid2": { ... },
    "uid3": { ... }
  }
}
现在考虑一下,使用这个工作的CloudFireStore数据库结构,我启动了我的第一个移动应用程序版本。然后,在某个时刻,我意识到我想要包括另一个字段,例如
last\u login

在代码中,使用Java从Firestore DB读取所有用户文档的过程如下

FirebaseFirestore.getInstance().collection("users").get()
        .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
            @Override
            public void onComplete(@NonNull Task<QuerySnapshot> task) {
                if (task.isSuccessful()) {
                    for (DocumentSnapshot document : task.getResult()) {
                        mUsers.add(document.toObject(User.class));
                    }
                }
            }
        });
FirebaseFirestore.getInstance().collection(“用户”).get()
.addOnCompleteListener(新的OnCompleteListener(){
@凌驾
未完成的公共void(@NonNull任务){
if(task.issusccessful()){
对于(DocumentSnapshot文档:task.getResult()){
添加(document.toObject(User.class));
}
}
}
});
其中类
用户
现在包含
名称
电子邮件
上次登录

由于数据库中存储的旧用户中不包括新的
User
字段(
last\u login
),因此应用程序正在崩溃,因为新的
User
类需要一个
last\u login
字段,该字段通过
get()
方法返回为
null


在数据库的所有现有
用户
文档中包含
上次登录
的最佳实践是什么,而不会丢失新版本应用程序中的数据?我应该只运行一次代码片段来完成此任务,还是有更好的方法来解决此问题?

您陷入了NOSQL数据库的空白:面向文档的数据库不能保证数据的结构完整性(就像RDBMS那样)

交易是:

  • 在RDBMS中,所有存储的数据在任何给定时间(在同一实例或集群内)都具有相同的结构。更改结构(ER图)时,必须迁移所有现有记录的数据,这需要花费时间和精力

    因此,您的应用程序可以针对数据结构的当前版本进行优化

  • 在面向文档的数据库中,每个记录都是一个独立的“页面”,具有自己独立的结构。如果更改结构,则仅适用于新文档。因此,您不需要迁移现有数据

    所以,您的应用程序必须能够处理您在当前数据库中使用过的数据结构的所有版本


我不知道firebase的详细情况,但一般来说,您从不更新NOSQL数据库中的文档。您只能创建文档的新版本。因此,即使您更新了所有文档,您的应用程序也必须准备好处理“旧”数据结构…

要解决这个问题,您需要更新每个用户以获得新属性,为此,我建议您使用
映射。如果您在创建用户时使用模型类,如我在此中的回答所述,要更新所有用户,只需迭代
users
集合,并使用以下代码:

Map<String, Object> map = new HashMap<>();
map.put("timestamp", FieldValue.serverTimestamp());
userDocumentReference.set(map, SetOptions.merge());
Map Map=newhashmap();
put(“timestamp”,FieldValue.serverTimestamp());
userDocumentReference.set(映射,SetOptions.merge());

我猜
上次登录
是一种原始数据类型,可能是一种保存时间戳的
。自动生成的setter如下所示:

private long last_login;

public void setLast_login(long last_login) {
    this.last_login = last_login;
}
当由于对原始数据类型的变量赋值为空而获取缺少该字段的旧文档时,这会导致崩溃。 一种解决方法是修改setter以传入等效包装类的变量-
Long
,而不是本例中的
Long
,并在setter中放入null检查

private long last_login;

public void setLast_login(Long last_login) {
    if(last_login != null) {
        this.last_login = last_login;
    }
}

避免空指针异常的代价是装箱拆箱开销。

我在发布问题时编写了一些例程来帮助自动化此过程。我没有发布它们,因为它们有点初级,我希望有一个优雅的基于Firestore的解决方案。因为这样的解决方案还不可用,下面是我编写的函数

简而言之,我们有重命名字段、添加字段或删除字段的函数。要重命名字段,将根据数据类型使用不同的函数。也许有人能更好地概括这一点?以下功能是:

  • 添加字段:在集合的所有文档中添加字段
  • 删除字段:删除集合中所有文档中的字段
  • 重命名字段:重命名集合中所有文档中包含特定数据类型(*)的字段。这里我包括字符串、整数和日期的示例
添加字段

public void add_field (final String key, final Object value, final String collection_ref) {
    FirebaseFirestore.getInstance().collection(collection_ref).get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {
                        WriteBatch batch = db.batch();

                        for (DocumentSnapshot document : task.getResult()) {
                            DocumentReference docRef = document.getReference();
                            Map<String, Object> new_map = new HashMap<>();
                            new_map.put(key, value);
                            batch.update(docRef, new_map);
                        }
                        batch.commit();
                    } else {
                        // ... "Error adding field -> " + task.getException()
                    }
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // ... "Failure getting documents -> " + e
                }
            });
}
public void delete_field (final String key, final String collection_ref) {
    FirebaseFirestore.getInstance().collection(collection_ref).get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {

                        WriteBatch batch = db.batch();

                        for (DocumentSnapshot document : task.getResult()) {
                            DocumentReference docRef = document.getReference();
                            Map<String, Object> delete_field = new HashMap<>();
                            delete_field.put(key, FieldValue.delete());
                            batch.update(docRef, delete_field);
                        }
                        // Commit the batch
                        batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
                            @Override
                            public void onComplete(@NonNull Task<Void> task) {
                                // ...
                            }
                        });

                    } else {
                        // ... "Error updating field -> " + task.getException()
                    }
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // ... "Failure getting notices -> " + e
                }
            });
}
public void rename_string_field (final String old_key, final String new_key, final String collection_ref) {
    FirebaseFirestore.getInstance().collection(collection_ref).get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {

                        WriteBatch batch = db.batch();

                        for (DocumentSnapshot document : task.getResult()) {
                            DocumentReference docRef = document.getReference();
                            String old_value = document.getString(old_key);

                            if (old_value != null) {
                                Map<String, Object> new_map = new HashMap<>();
                                new_map.put(new_key, old_value);

                                Map<String, Object> delete_old = new HashMap<>();
                                delete_old.put(old_key, FieldValue.delete());

                                batch.update(docRef, new_map);
                                batch.update(docRef, delete_old);
                            }
                        }
                        // Commit the batch
                        batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
                            @Override
                            public void onComplete(@NonNull Task<Void> task) {
                                // ...
                            }
                        });

                    } else {
                        // ... "Error updating field -> " + task.getException()
                    }
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // ... "Failure getting notices ->" + e
                }
            });
}

public void rename_integer_field (final String old_key, final String new_key, final String collection_ref) {
    FirebaseFirestore.getInstance().collection(collection_ref).get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {

                        WriteBatch batch = db.batch();

                        for (DocumentSnapshot document : task.getResult()) {
                            DocumentReference docRef = document.getReference();
                            int old_value = document.getDouble(old_key).intValue();
                            Integer ov = old_value;
                            if (ov != null) {
                                Map<String, Object> new_map = new HashMap<>();
                                new_map.put(new_key, old_value);

                                Map<String, Object> delete_old = new HashMap<>();
                                delete_old.put(old_key, FieldValue.delete());

                                batch.update(docRef, new_map);
                                batch.update(docRef, delete_old);
                            }
                        }
                        // Commit the batch
                        batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
                            @Override
                            public void onComplete(@NonNull Task<Void> task) {
                                // ...
                            }
                        });

                    } else {
                        // ... "Error updating field -> " + task.getException()
                    }
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // ... "Failure getting notices -> " + e
                }
            });
}

public void rename_date_field (final String old_key, final String new_key, final String collection_ref) {
    FirebaseFirestore.getInstance().collection(collection_ref).get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {

                        WriteBatch batch = db.batch();

                        for (DocumentSnapshot document : task.getResult()) {
                            DocumentReference docRef = document.getReference();
                            Date old_value = document.getDate(old_key);
                            if (old_value != null) {
                                Map<String, Object> new_map = new HashMap<>();
                                new_map.put(new_key, old_value);

                                Map<String, Object> delete_old = new HashMap<>();
                                delete_old.put(old_key, FieldValue.delete());

                                batch.update(docRef, new_map);
                                batch.update(docRef, delete_old);
                            }
                        }
                        // Commit the batch
                        batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
                            @Override
                            public void onComplete(@NonNull Task<Void> task) {
                                // ...
                            }
                        });

                    } else {
                        // ... "Error updating field -> " + task.getException()
                    }
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // ... "Failure getting notices -> " + e
                }
            });
}
public void add_字段(最终字符串键、最终对象值、最终字符串集合\u ref){
FirebaseFirestore.getInstance().collection(collection\u ref.get())
.addOnCompleteListener(新的OnCompleteListener(){
@凌驾
未完成的公共void(@NonNull任务){
if(task.issusccessful()){
WriteBatch batch=db.batch();
对于(DocumentSnapshot文档:task.getResult()){
DocumentReference docRef=document.getReference();
Map new_Map=new HashMap();
新映射放置(键、值);
批量更新(docRef,新地图);
}
batch.commit();
}否则{
//…“添加字段->时出错”+task.getException()
}
}
})
.addOnFailureListener(新的OnFailureListener(){
@凌驾
public void onFailure(@NonNull异常e){