添加新字段或更改所有Firestore文档的结构

问题描述 投票:4回答:3

考虑一下users的集合。该集合中的每个文档都有nameemail作为字段。

{
  "users": {
    "uid1": {
      "name": "Alex Saveau",
      "email": "[email protected]"
    },
    "uid2": { ... },
    "uid3": { ... }
  }
}

现在考虑一下,通过这个有效的Cloud Firestore数据库结构,我启动了我的第一个版本的移动应用程序。然后,在某些时候我意识到我想要包括另一个领域,如last_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));
                    }
                }
            }
        });

User现在包含nameemaillast_login

由于新的User字段(last_login)不包含在存储在DB中的旧用户中,因此应用程序崩溃,因为新的User类期望last_login字段,该字段由null方法返回为get()

在数据库的所有现有last_login文档中包含User而不丢失新版本应用程序的数据的最佳做法是什么?我应该只运行一次片段来执行此任务,还是有更好的方法来解决问题?

java android database google-cloud-firestore
3个回答
5
投票

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

这笔交易是:

  • 在RDBMS中,所有存储的数据在任何给定时间(在同一实例或集群内)具有相同的结构。更改结构(ER图)时,您必须迁移所有现有记录的数据,这些记录需要花费时间和费用。 结果,您可以针对当前版本的数据结构优化应用程序。
  • 在面向文档的数据库中,每个记录都是一个独立的“页面”,具有自己独立的结构。如果您更改了敲击,它只适用于新文件。因此,您无需迁移现有数据。 结果,您的应用程序必须能够处理您在当前数据库中使用过的所有数据结构版本。

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


1
投票

我猜last_login是一种原始数据类型,也许是一个long来保存时间戳。自动生成的setter看起来像这样:

private long last_login;

public void setLast_login(long last_login) {
    this.last_login = last_login;
}

当由于对原始数据类型的变量的null赋值而获取缺少该字段的旧文档时,这会导致崩溃。解决它的一种方法是修改你的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;
    }
}

避免空指针异常的成本是装箱拆箱开销。


1
投票

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

Map<String, Object> map = new HashMap<>();
map.put("timestamp", FieldValue.serverTimestamp());
userDocumentReference.set(map, SetOptions.merge());
© www.soinside.com 2019 - 2024. All rights reserved.