Firebase实时数据库添加设备前校验存在性:事务实现异常
问题描述

我希望在向Firebase实时数据库添加特定设备前,先检查该设备是否已存在。曾尝试使用ValueEventListener和SingleValueEventListener,但二者均存在竞态条件问题。改用Firebase事务后,方案仍未生效。
我的代码如下:
Devices devices=new Devices(devicename,timein,email,timeout); myRef.child("Device-1").runTransaction(new Transaction.Handler() { @Override public Transaction.Result doTransaction(MutableData mutableData) { if (mutableData.getValue() != devicename) { myRef.child("Device-1").push().setValue(devices); // Data does not exist, add it return Transaction.success(mutableData); } return Transaction.abort(); } @Override public void onComplete(DatabaseError error, boolean committed, DataSnapshot snapshot) { if (error != null) { // Handle error } } });
原本预期数据库中已有相同devicename的条目时,第二次不会添加数据,但实际未达到该效果。
问题分析
代码存在两个核心问题:
- 事务逻辑失效:在事务内部直接调用
myRef.child("Device-1").push().setValue(devices),相当于绕过事务的原子控制,在事务外部执行写入操作,完全失去了事务的意义。 - 存在性判断错误:
mutableData.getValue() != devicename的逻辑不成立——mutableData是Device-1节点下的所有数据集合,直接与设备名字符串对比完全不匹配;且用push()生成随机ID节点后,无法通过Device-1根节点直接判断指定设备是否存在。
正确解决方案
方案1:用设备名作为节点键(推荐)
利用Firebase的原子写入特性,将设备名作为节点的唯一键,既能避免竞态条件,又能高效判断设备是否存在:
// 仅在设备不存在时写入,不覆盖已有数据 myRef.child("Device-1").child(devicename).runTransaction(new Transaction.Handler() { @Override public Transaction.Result doTransaction(MutableData mutableData) { if (mutableData.getValue() == null) { // 设备不存在,写入数据 mutableData.setValue(devices); return Transaction.success(mutableData); } // 设备已存在,终止事务 return Transaction.abort(); } @Override public void onComplete(DatabaseError error, boolean committed, DataSnapshot snapshot) { if (error != null) { // 处理错误 } else if (committed) { // 设备成功添加 } else { // 设备已存在,未执行添加 } } });
如果允许覆盖已有设备数据,可直接用原子写入,无需事务:
myRef.child("Device-1").child(devicename).setValue(devices, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { if (error != null) { // 写入失败处理 } else { // 写入成功,可通过监听判断是否为新添加设备 ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { boolean isNewDevice = snapshot.getChildrenCount() == 1; // 根据业务逻辑处理 } @Override public void onCancelled(DatabaseError error) { // 错误处理 } }); } } });
方案2:保留push()随机ID的场景
若业务必须使用随机ID节点,需在事务内遍历所有子节点检查设备名:
myRef.child("Device-1").runTransaction(new Transaction.Handler() { @Override public Transaction.Result doTransaction(MutableData mutableData) { boolean deviceExists = false; // 遍历子节点检查是否存在同名设备 for (MutableData child : mutableData.getChildren()) { Devices existingDevice = child.getValue(Devices.class); if (existingDevice != null && existingDevice.getDevicename().equals(devicename)) { deviceExists = true; break; } } if (!deviceExists) { // 生成随机ID并添加新设备 String newKey = myRef.child("Device-1").push().getKey(); mutableData.child(newKey).setValue(devices); return Transaction.success(mutableData); } return Transaction.abort(); } @Override public void onComplete(DatabaseError error, boolean committed, DataSnapshot snapshot) { if (error != null) { // 错误处理 } else if (committed) { // 添加成功 } else { // 设备已存在 } } });
注意:此方案在Device-1节点数据量较大时性能会下降,优先推荐方案1。
内容的提问来源于stack exchange,提问作者laos
相关产品推荐
相关产品推荐

