野狗并没有提供一个类似于count()的API。当我们需要知道某个数据节点下有多少个子节点的时候,有两种方式来实现:
-
当数据量不大的时候,可以在客户端进行计数
-
当数据量较大的时候,在野狗云端记录一个计数器
由于JavaScript语言比较容易运行和调试,我们以JavaScript SDK为例来解决这个问题,Android和iOS端可以实现相同的效果。
在客户端计数
假设一个这样的场景,我们有一个/books数据节点,通过push()方法在其下存储了许多书籍的信息,我们将每本书的信息称之为一条“记录”,数据结构如下:
我们先假设记录条数量很少,采用客户端计数的方案。需要注意的是,使用child_added事件是无法实现这个计数需求的:
var ref = new Wilddog("https://<appId>.wilddogio.com/books");
var counter = 0;
ref.on('child_added', function(snapshot) {
console.log(snapshot.val().name);
counter ++;
});
在上面的代码中,由于child_added事件会为每个子节点触发一次,因此我们试图用一个counter字段作为计数器来计算总记录的条数。这个方案看起来还可以,但是问题出在异步上,每此回调函数被触发执行,我们都无法知道这是否是最后一条记录。
正确的做法是使用value事件:
var ref = new Wilddog("https://<appId>.wilddogio.com/books");
ref.on('value', function(snapshot) {
var count = 0;
snapshot.forEach(function() {
count++;
});
console.log(count); // 此时的count是可靠的
});
上面的代码可以准确的统计到记录的条数,其实还有一个更简单的API,不需要使用forEach循环计数,直接调用snapshot的numChildren()方法就可以了。这是小数据量的时候最好的方法了,非常简单。
在云端计数
遗憾的是实际的应用开发中,大部分时候我们都会考虑数据量比较大,全部加载到客户端进行计数是效率非常低,甚至完全不可行的。我们可以在云端记录一个计数器的方式来实现。我们在books节点下增加一个count节点,代表记录的条数,数据结构如下:
接下来,在每次push()一条新的记录的时候,我们都对counter字段进行+1:
var ref = new Wilddog("https://<appId>.wilddogio.com/books");
ref.push({"name":'new book'});
ref.child('counter').once('value', function(snapshot) {
var count = snapshot.val();
counter ++;
ref.child('counter').set(count);
});
这是最简单最直观的实现。但是它只适用于没有并发访问,或者对count数据的精确度要求不高的场景下。因为这里的push一条数据和修改counter的值是两个操作,而且修改counter的值是一种“读出来,加1,写回去”的模式,在多个终端用户同时操作的情况下会出现并发问题导致数据错误。
要解决并发修改数据的问题,需要使用野狗的transaction API。这里的transaction和关系型数据库中事务不是一回事。关系型数据库中的事务是为了保证ACID四个特性,而野狗的transaction API只是用来保证在多个客户端同时修改一个数据时不会产生冲突,可以实现对数据更改的原子性操作。使用transaction后的代码实现如下:
ref.child('counter').transaction(function(currentValue) {
var newValue = (currentValue||0) + 1;
return newValue;
}, function(err, committed, snapshot) {
if (err) {
console.log("err: " + err);
} else if (committed) {
ref.push({"name":'a new book'});
}
});
transaction() 方法接受2个回调函数作为参数。第一个函数将会获取到当前的值,return的是即将被写入的值。这里我们对counter进行+1操作。第二个回调函数中的committed为true时,代表这个transaction已经成功,此时我们push一条新的记录。
transaction()的原理是,在向云端写入newValue的时候,会判断云端的数据仍然是旧的currentValue。如果云端数据已被其他客户端修改掉了,那么这次请求就会失败并重试。重试的过程是,重新从云端获取数据的值,记为currentValue,重新计算新值(+1),重新向云端写入。这个过程一直重复直至写入成功,或者超过最大重试次数。这些工作都是野狗SDK内部完成了的,我们无需关心其原理,只要根据文档,知道API的用法就可以了。
实现自增字段
有了计数器的实现,稍加修改就可以实现自增字段了。我们可以将counter字段作为一个id的发号器,用它来生成自增的id。假如我们不再使用push()方法存储每本书的数据(push会为每条记录生成唯一的key,就是数据中以-K开头的字符串),而是自行维护一个自增的“主键”。我们将写入数据的程序修改如下:
ref.child('counter').transaction(function(currentValue) {
return (currentValue||0) + 1;
}, function(err, committed, snapshot) {
if (err) {
console.log("err: " + err);
} else if (committed) {
var id = snapshot.val();
ref.child(id).set({"name":'a new book'});
}
});
执行的过程是,第一个回调函数将counter的值+1,成功之后进入第二个回调函数中,commited为true,snapshot为+1之后的counter的值,也就是获得了最新的id。不再使用push,而是child(id)之后去set()数据就可以了。结果如下:
这样我们就实现了自增字段。
转载自:https://sanwen8.cn/p/1a7EJbE.html