【发布时间】:2019-10-03 13:02:33
【问题描述】:
我想使用offical mongodb package 在node.js 环境中为mongodb 实现分页。我试图在互联网上找到,但都是基于mongoose 的链接。我不想使用猫鼬。
如何使用
http://mongodb.github.io/node-mongodb-native/3.1/api/提供的官方客户端 api 实现分页
【问题讨论】:
标签: node.js mongodb pagination
我想使用offical mongodb package 在node.js 环境中为mongodb 实现分页。我试图在互联网上找到,但都是基于mongoose 的链接。我不想使用猫鼬。
如何使用
http://mongodb.github.io/node-mongodb-native/3.1/api/提供的官方客户端 api 实现分页
【问题讨论】:
标签: node.js mongodb pagination
基于偏移量的方法有一个很大的缺陷:如果结果列表在调用 API 之间发生了变化,则索引会发生变化并导致项目被返回两次或被跳过并且永远不会返回
这个问题在 https://www.sitepoint.com/paginating-real-time-data-cursor-based-pagination/
基于时间的分页方法会好一点,因为不再跳过结果。如果您查询第一页,然后删除了一个新项目,它不会改变您第二页中的结果,一切都很好。但是,这种方法有一个重大缺陷:如果同时创建了多个项目怎么办?
最好使用基于光标的分页
可以使用集合中唯一、可排序和不可变的任何字段来实现。
_id 满足所有唯一、可订购和不可变条件。基于该字段,我们可以将最后一个文档的_id作为后续请求的光标进行排序并返回页面结果。
curl https://api.mixmax.com/items?limit=2
const items = db.items.find({}).sort({
_id: -1
}).limit(2);
const next = items[items.length - 1]._id
res.json({ items, next })
当用户想要获得第二个页面时,他们将光标(作为下一个)传递到 URL:
curl https://api.mixmax.com/items?limit=2&next=590e9abd4abbf1165862d342
const items = db.items.find({
_id: { $lt: req.query.next }
}).sort({
_id: -1
}).limit(2);
const next = items[items.length - 1]._id
res.json({ items, next })
如果我们想以不同的顺序返回结果,例如项目的日期,那么我们将在查询字符串中添加sort=launchDate。
curl https://api.mixmax.com/items?limit=2&sort=launchDate
const items = db.items.find({}).sort({
launchDate: -1
}).limit(2);
const next = items[items.length - 1].launchDate;
res.json({ items, next })
后续页面请求curl https://api.mixmax.com/items?limit=2&sort=launchDate&next=2017-09-11T00%3A44%3A54.036Z
const items = db.items.find({
launchDate: { $lt: req.query.next }
}).sort({
_id: -1
}).limit(2);
const next = items[items.length - 1].launchDate;
res.json({ items, next });
如果我们在同一天同一时间推出了一堆项目?现在我们的 launchDate 字段不再是唯一的,并且不满足 Unique、Orderable 和 Immutable。健康)状况。我们不能将它用作游标字段。但是我们可以使用两个字段来生成游标。既然我们知道 MongoDB 中的 _id 字段总是满足上述三个条件,我们知道如果我们将它与 launchDate 字段一起使用,这两个字段的组合将满足要求,可以一起用作游标字段。
curl https://api.mixmax.com/items?limit=2&sort=launchDate
const items = db.items.find({}).sort({
launchDate: -1,
_id: -1 // secondary sort in case there are duplicate launchDate values
}).limit(2);
const lastItem = items[items.length - 1];
// The cursor is a concatenation of the two cursor fields, since both are needed to satisfy the requirements of being a cursor field
const next = `${lastItem.launchDate}_${lastItem._id}`;
res.json({ items, next });
后续页面请求curl https://api.mixmax.com/items?limit=2&sort=launchDate&next=2017-09-11T00%3A44%3A54.036Z_590e9abd4abbf1165862d342
const [nextLaunchDate, nextId] = req.query.next.split(‘_’);
const items = db.items.find({
$or: [{
launchDate: { $lt: nextLaunchDate }
}, {
// If the launchDate is an exact match, we need a tiebreaker, so we use the _id field from the cursor.
launchDate: nextLaunchDate,
_id: { $lt: nextId }
}]
}).sort({
_id: -1
}).limit(2);
const lastItem = items[items.length - 1];
// The cursor is a concatenation of the two cursor fields, since both are needed to satisfy the requirements of being a cursor field
const next = `${lastItem.launchDate}_${lastItem._id}`;
res.json({ items, next });
参考:https://engineering.mixmax.com/blog/api-paging-built-the-right-way/
【讨论】:
使用推荐的分页方法与limit() 和skip() (see here):
const MongoClient = require('mongodb').MongoClient;
MongoClient.connect('http:localhost:27017').then((client) => {
const db = client.db(mongo.db);
db.collection('my-collection').find({}, {limit:10, skip:0}).then((documents) => {
//First 10 documents
console.log(documents);
});
db.collection('my-collection').find({}, {limit:10, skip:10}).then((documents) => {
//Documents 11 to 20
console.log(documents);
});
});
这是一个分页功能:
function studentsPerPage (pageNumber, nPerPage) {
return db.collection('students').find({},
{
limit: nPerPage,
skip: pageNumber > 0 ? ( ( pageNumber - 1 ) * nPerPage ) : 0
});
}
【讨论】:
{_id: {$lte: previousId}}。
skip 方法虽然简单明了,但如果大多数人不打算翻阅大量文档,则可能不值得担心。
我正在发送一个在 MongoDb 和 Nodejs 上的 API。
module.exports.fetchLoans = function(req, res, next) {
var perPage = 5;
var page = req.body.page || 1;
loans
.find({ userId: req.user._id})
.select("-emi")
.skip(perPage * page - perPage)
.limit(perPage)
.sort({ timestamp: -1 })
.exec(function(err, loan) {
if (loan != null) {
loans
.find({ userId: req.user._id})
.count()
.exec(function(err, count) {
if (count != null) {
res.json({
success: true,
loans: loan,
currentpage: page,
totalpages: Math.ceil(count / perPage)
});
} else {
console.log("Milestone Error: ", err);
res.json({ success: false, error: "Internal Server Error. Please try again." });
}
});
} else {
console.log("Milestone Error: ", err);
res.json({ success: false, error: "Internal Server Error. Please try again." });
}
});
};
在此代码中,您必须在每次点击时提供页码。
【讨论】:
你可以使用skip和limit选项来实现分页
module.exports = (data)=>{
let page = parseInt(data.page);
let limit = parseInt(data.limit);
let skip = 0
if(page>1){
skip = (page * limit);
}
let mongoClient = require('mongodb').MongoClient;
mongoClient.connect('mongodb://localhost:27017').then((client) => {
let db = client.db('your-db');
db.collection('your-collection').find({}, {limit:limit, skip:skip}).then((documents) => {
console.log(documents);
});
});
};
【讨论】: