考虑一下users
的集合。该集合中的每个文档都有name
和email
作为字段。
{
"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
现在包含name
,email
和last_login
。
由于新的User
字段(last_login
)不包含在存储在DB中的旧用户中,因此应用程序崩溃,因为新的User
类期望last_login
字段,该字段由null
方法返回为get()
。
在数据库的所有现有last_login
文档中包含User
而不丢失新版本应用程序的数据的最佳做法是什么?我应该只运行一次片段来执行此任务,还是有更好的方法来解决问题?
您陷入了NOSQL数据库的空白:面向文档的数据库不保证数据的结构完整性(如RDBMS那样)
这笔交易是:
我不详细了解firebase,但一般来说,你永远不会更新NOSQL数据库中的文档。您只能创建该文档的新版本。因此,即使您更新所有文档,您的应用程序也必须准备好处理“旧”数据结构...
我猜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;
}
}
避免空指针异常的成本是装箱拆箱开销。
要解决此问题,您需要更新每个用户以获得新属性,为此我建议您使用Map
。如果您在创建用户时使用模型类,如我在post的回答中所述,要更新所有用户,只需迭代users
集合并使用以下代码:
Map<String, Object> map = new HashMap<>();
map.put("timestamp", FieldValue.serverTimestamp());
userDocumentReference.set(map, SetOptions.merge());