考虑users
的集合。该集合中的每个文档都以name
和email
为字段。
{
"users": {
"uid1": {
"name": "Alex Saveau",
"email": "saveau.alexandre@gmail.com"
},
"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
)未包含在存储在数据库中的旧用户中,因此应用程序崩溃,因为新的User
类期望{{1由last_login
方法返回为null
的字段。
在数据库的所有现有get()
文档中包含last_login
而不会丢失新应用版本数据的最佳做法是什么?我应该只运行一次片段来执行此任务,还是有更好的解决方法?
答案 0 :(得分:6)
当我发布问题时,我编写了一些例程来帮助自动执行此过程。我没有发布它们,因为它们有点基本,我希望有一个基于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
}
});
}
答案 1 :(得分:5)
你陷入了NOSQL数据库的空白:面向文档的数据库不保证数据的结构完整性(如RDBMS那样)
这笔交易是:
在 RDBMS 中,所有存储的数据在任何给定时间(在同一实例或群集中)具有相同的结构。更改结构(ER图)时,您必须迁移所有现有记录的数据,这些记录需要花费时间和费用。
结果,您可以针对当前版本的数据结构优化应用程序。
在面向文档的数据库中,每条记录都是一个独立的&#34; Page&#34;拥有独立的结构。如果您更改了触发器,它只适用于 new 文档。因此,您不需要迁移现有数据。
结果,您的应用程序必须能够处理您在当前数据库中使用的所有数据结构版本。
我不详细了解firebase,但一般情况下,您永远不会在NOSQL数据库中更新文档。您只能创建该文档的新版本。因此,即使您更新所有文档,您的申请也必须准备好以处理旧的&#34;数据结构......
答案 2 :(得分:1)
要解决此问题,您需要更新每个用户以获得新属性,为此我建议您使用Map
。如果您在创建用户时使用模型类,如我在 post 的回答中所述,要更新所有用户,只需迭代users
集合并使用以下代码:
Map<String, Object> map = new HashMap<>();
map.put("timestamp", FieldValue.serverTimestamp());
userDocumentReference.set(map, SetOptions.merge());
答案 3 :(得分:1)
我猜想last_login
是原始数据类型,也许是long
用来保存时间戳。自动生成的二传手将如下所示:
private long last_login;
public void setLast_login(long last_login) {
this.last_login = last_login;
}
当由于缺少对原始数据类型变量的空分配而获取缺少该字段的旧文档时,这将导致崩溃。
解决该问题的一种方法是修改您的设置器,以传入等效包装器类的变量-在这种情况下,使用Long
而不是long
,然后在设置器中放置一个空检查。
private long last_login;
public void setLast_login(Long last_login) {
if(last_login != null) {
this.last_login = last_login;
}
}
避免空指针异常的代价是装箱-拆箱的开销。
答案 4 :(得分:0)
只想分享一下,因为我读到您希望基于Firestore的解决方案。
这对我有用。 forEach将查询集合中的每个文档,您可以根据需要进行操作。
db.collection("collectionName").get().then(function(querySnapshot) {
querySnapshot.forEach(async function(doc) {
await db.collection("collectionName").doc(doc.id).set({newField: value}, {merge: true});
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
});