常用的sqlite轻量级数据库,nodejs在windows环境下安装配置有时候会比较麻烦,很难顺利安装。
Nedb(Node Embedded Database)在一些情况下可以替代sqlite,特别适用于小型项目和快速原型开发。本文将介绍Nedb的基本概念、特性和使用方法,以帮助大家更好地了解和利用这个便捷的工具。
github官网
Nedb是一个基于Node.js的嵌入式数据库,它在内存中存储数据,同时也支持将数据持久化到磁盘。它的设计灵感来自MongoDB,提供了类似的API和查询语言,而且在运行程序时不用启动独立的数据库服务器。
Nedb的主打特性之一就是其轻量级,这使得它成为处理小型项目、原型开发或者快速测试的理想选择。不需要复杂的设置和配置,你可以迅速开始使用它,加速你的开发流程。
Nedb的API设计简单易用,与MongoDB的API有很多相似之处。这意味着如果你熟悉MongoDB,你将很容易上手Nedb。基本的CRUD操作都得到了良好的支持,让你能够高效地管理你的数据。
Nedb支持将数据保存在内存中,也支持将数据持久化到磁盘。这样你可以根据你的项目需求选择合适的存储方式,既可以追求更高的性能,也可以确保数据的持久性。
使用npm进行安装:
npm install nedb
const Datastore = require('nedb');
const db = new Datastore({ filename: 'path/to/database.db', autoload: true });
// 仅内存数据库(无需加载数据库)
const Datastore = require('nedb');
const db = new Datastore();
// 持久数据库,手动加载
const Datastore = require('nedb');
const db = new Datastore({ filename: 'path/to/datafile' });
db.loadDatabase(function (err) {
// 现在可以执行命令
});
// 持久数据库,自动加载
const Datastore = require('nedb');
const db = new Datastore({ filename: 'path/to/datafile', autoload: true });
// 可以立即发出命令
// Node Webkit应用程序的持久数据库(已弃用)
const Datastore = require('nedb');
const path = require('path');
const db = new Datastore({ filename: path.join(require('nw.gui').App.dataPath, 'something.db') });
如果需要多个数据集合,通常最好为所有集合使用autoload
。下面是创建多个数据库实例的示例,通常建议使用autoload
为所有集合加载数据库:
const db = {};
db.users = new Datastore('path/to/users.db');
db.robots = new Datastore('path/to/robots.db');
// 异步加载每个数据库
db.users.loadDatabase();
db.robots.loadDatabase();
const newData = { name: 'John Doe', age: 25, city: 'New York' };
db.insert(newData, (err, insertedData) => {
if (err) {
console.error(err);
} else {
console.log('Data inserted:', insertedData);
}
});
下面是官网的一些示例:
// 假设数据库有这些数据
// { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false, satellites: ['Phobos', 'Deimos'] }
// { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true, humans: { genders: 2, eyes: true } }
// { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }
// { _id: 'id4', planet: 'Omicron Persei 8', system: 'futurama', inhabited: true, humans: { genders: 7 } }
// { _id: 'id5', completeData: { planets: [ { name: 'Earth', number: 3 }, { name: 'Mars', number: 2 }, { name: 'Pluton', number: 9 } ] } }
// 查找所有太阳系统行星
db.find({ system: 'solar' }, function (err, docs) {
// docs is an array containing documents Mars, Earth, Jupiter
// If no document is found, docs is equal to []
});
// 查询找名称里包含ar的,使用正则表达式查询
db.find({ planet: /ar/ }, function (err, docs) {
// docs contains Mars and Earth
});
// 查找从太阳系继承的
db.find({ system: 'solar', inhabited: true }, function (err, docs) {
// docs is an array containing document Earth only
});
// 使用点分匹配多级的文档
db.find({ "humans.genders": 2 }, function (err, docs) {
// docs contains Earth
});
// 使用点分来索引子文档数组
db.find({ "completeData.planets.name": "Mars" }, function (err, docs) {
// docs contains document 5
});
db.find({ "completeData.planets.name": "Jupiter" }, function (err, docs) {
// docs is empty
});
db.find({ "completeData.planets.0.name": "Earth" }, function (err, docs) {
// docs contains document 5
// If we had tested against "Mars" docs would be empty because we are matching against a specific array element
});
// 深度比较,与点分有所不同
db.find({ humans: { genders: 2 } }, function (err, docs) {
// docs is empty, because { genders: 2 } is not equal to { genders: 2, eyes: true }
});
// 返回所有数据
db.find({}, function (err, docs) {
});
// The same rules apply when you want to only find one document
db.findOne({ _id: 'id1' }, function (err, doc) {
// doc is the document Mars
// If no document is found, doc is null
});
查询操作支持一些运算符,如大于、小于、包含、存在、正则表达式等,示例如下:
// 数值比较
db.find({ "humans.genders": { $gt: 5 } }, function (err, docs) {
// docs contains Omicron Persei 8, whose humans have more than 5 genders (7).
});
// 字符串比较
db.find({ planet: { $gt: 'Mercury' }}, function (err, docs) {
// docs contains Omicron Persei 8
})
// in 包含
db.find({ planet: { $in: ['Earth', 'Jupiter'] }}, function (err, docs) {
// docs contains Earth and Jupiter
});
// 是否存在
db.find({ satellites: { $exists: true } }, function (err, docs) {
// docs contains only Mars
});
// 联合使用操作符
db.find({ planet: { $regex: /ar/, $nin: ['Jupiter', 'Earth'] } }, function (err, docs) {
// docs only contains Mars because Earth was excluded from the match by $nin
});
当使用NeDB进行查询(find
、findOne
或count
)而没有指定回调函数时,会返回一个Cursor对象。可以使用sort
、skip
和limit
来修改游标,然后使用exec(callback)
来执行。
// 假设数据库包含以下4个文档
// doc1 = { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false, satellites: ['Phobos', 'Deimos'] }
// doc2 = { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true, humans: { genders: 2, eyes: true } }
// doc3 = { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }
// doc4 = { _id: 'id4', planet: 'Omicron Persei 8', system: 'futurama', inhabited: true, humans: { genders: 7 } }
// 在没有使用查询条件的情况下,返回所有结果(在游标修改器之前)
db.find({}).sort({ planet: 1 }).skip(1).limit(2).exec(function (err, docs) {
// docs 是 [doc3, doc1]
});
// 可以按照反向顺序排序
db.find({ system: 'solar' }).sort({ planet: -1 }).exec(function (err, docs) {
// docs 是 [doc1, doc3, doc2]
});
// 可以按照一个字段,然后按照另一个字段排序,以此类推
db.find({}).sort({ firstField: 1, secondField: -1 }) ... // 你明白这是如何工作的!
这段代码展示了如何使用NeDB进行排序和分页。通过sort
方法,可以按照指定字段对结果进行排序,而skip
和limit
则用于分页。这提供了在NeDB中自定义返回结果顺序和数量的灵活性。
投影是指在查询时指定返回结果中的字段,NeDB提供了投影的功能,类似于MongoDB。投影的语法为:{ a: 1, b: 1 }
表示只返回a和b字段,{ a: 0, b: 0 }
表示省略a和b字段。不同的是,你不能同时使用这两种模式,除非是_id
字段,默认情况下总是返回的,但你可以选择省略。投影也可以用于嵌套文档。
// 以上述数据库为例
// 仅保留给定的字段
db.find({ planet: 'Mars' }, { planet: 1, system: 1 }, function (err, docs) {
// docs 是 [{ planet: 'Mars', system: 'solar', _id: 'id1' }]
});
// 仅保留给定的字段,但省略 _id
db.find({ planet: 'Mars' }, { planet: 1, system: 1, _id: 0 }, function (err, docs) {
// docs 是 [{ planet: 'Mars', system: 'solar' }]
});
// 仅省略给定的字段,并删除 _id
db.find({ planet: 'Mars' }, { planet: 0, system: 0, _id: 0 }, function (err, docs) {
// docs 是 [{ inhabited: false, satellites: ['Phobos', 'Deimos'] }]
});
// 失败示例:同时使用两种模式
db.find({ planet: 'Mars' }, { planet: 0, system: 1 }, function (err, docs) {
// err 是错误消息,docs 是 undefined
});
// 也可以以 Cursor 方式使用,但这种语法与 MongoDB 不兼容
db.find({ planet: 'Mars' }).projection({ planet: 1, system: 1 }).exec(function (err, docs) {
// docs 是 [{ planet: 'Mars', system: 'solar', _id: 'id1' }]
});
// 对嵌套文档进行投影
db.findOne({ planet: 'Earth' }).projection({ planet: 1, 'humans.genders': 1 }).exec(function (err, doc) {
// doc 是 { planet: 'Earth', _id: 'id2', humans: { genders: 2 } }
});
这段代码演示了如何在NeDB中使用投影,通过指定查询时返回的字段,可以灵活地控制结果的结构。
// Count all planets in the solar system
db.count({ system: 'solar' }, function (err, count) {
// count equals to 3
});
// Count all documents in the datastore
db.count({}, function (err, count) {
// count equals to 4
});
db.update(query, update, options, callback)
指定文档应如何修改。它可以是一个新文档,也可以是一组修饰符,但不能同时使用。
可用的字段修饰符有
$set
(更改字段的值)$unset
(删除字段)$inc
(递增字段的值)$min/$max
仅在提供的值小于/大于当前值时更改字段的值。对于数组,有
$push
$pop
$addToSet
$pull
$each
$slice
是一个具有两个可能参数的对象
一些示例:
// 使用与“查找文档”部分相同的示例集合
// { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false }
// { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true }
// { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }
// { _id: 'id4', planet: 'Omicron Persia 8', system: 'futurama', inhabited: true }
// 替换文档
db.update({ planet: 'Jupiter' }, { planet: 'Pluto'}, {}, function (err, numReplaced) {
// numReplaced = 1
// 文档 #3 已被替换为 { _id: 'id3', planet: 'Pluto' }
// 请注意,_id 保持不变,文档已被替换('system' 和 'inhabited' 字段不再存在)
});
// 设置现有字段的值
db.update({ system: 'solar' }, { $set: { system: 'solar system' } }, { multi: true }, function (err, numReplaced) {
// numReplaced = 3
// Mars、Earth、Jupiter 上的 'system' 字段现在具有值 'solar system'
});
// 在子文档中使用点符号表示法设置不存在字段的值
db.update({ planet: 'Mars' }, { $set: { "data.satellites": 2, "data.red": true } }, {}, function () {
// Mars 文档现在是 { _id: 'id1', system: 'solar', inhabited: false
// , data: { satellites: 2, red: true }
// }
// 请注意,要设置子文档中的字段,您必须使用点符号表示法
// 使用对象表示法将仅替换顶级字段
db.update({ planet: 'Mars' }, { $set: { data: { satellites: 3 } } }, {}, function () {
// Mars 文档现在是 { _id: 'id1', system: 'solar', inhabited: false
// , data: { satellites: 3 }
// }
// 您失去了“data.red”字段,这可能不是预期的行为
});
});
// 删除字段
db.update({ planet: 'Mars' }, { $unset: { planet: true } }, {}, function () {
// 现在 Mars 文档不包含 planet 字段
// 您当然也可以使用点符号表示法取消嵌套字段
});
// Upsert 文档
db.update({ planet: 'Pluto' }, { planet: 'Pluto', inhabited: false }, { upsert: true }, function (err, numReplaced, upsert) {
// numReplaced = 1, upsert = { _id: 'id5', planet: 'Pluto', inhabited: false }
// 已向集合添加了新文档 { _id: 'id5', planet: 'Pluto', inhabited: false }
});
// 如果使用修饰符进行 upsert,那么 upserted 文档是由修饰符修改的查询
// 这比听起来简单:)
db.update({ planet: 'Pluto' }, { $inc: { distance: 38 } }, { upsert: true }, function () {
// 添加了新文档 { _id: 'id5', planet: 'Pluto', distance: 38 }
});
// 如果
db.remove(query, options, callback)
将根据选项删除与查询匹配的所有文档:
query
:与用于查找和更新的查询相同options
:目前只有一个选项:multi
,如果设置为 true
,允许删除多个文档。默认为 false
callback
:可选,签名为:(err, numRemoved)
以下是一些删除文档的示例:
// 使用与“查找文档”部分相同的示例集合
// { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false }
// { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true }
// { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }
// { _id: 'id4', planet: 'Omicron Persia 8', system: 'futurama', inhabited: true }
// 从集合中删除一个文档
// 选项设置为 {},因为默认情况下 multi 为 false
db.remove({ _id: 'id2' }, {}, function (err, numRemoved) {
// numRemoved = 1
});
// 删除多个文档
db.remove({ system: 'solar' }, { multi: true }, function (err, numRemoved) {
// numRemoved = 3
// 所有来自太阳系的行星都被移除了
});
// 移除具有“match-all”查询的所有文档
db.remove({}, { multi: true }, function (err, numRemoved) {
});
使用 datastore.ensureIndex(options, cb)
创建索引。
ensureIndex 可以在需要时调用,即使已经插入了一些数据,但最好在应用程序启动时调用。
参数 options 选项包括:
删除索引使用 datastore.removeIndex(fieldName, cb)
。
如果数据存储是持久的,则创建的索引将保存在数据文件中,当第二次加载数据库时,索引会自动创建。
示例:
// 创建索引
db.ensureIndex({ fieldName: 'somefield' }, function (err) {
// 如果出错,err 不为 null
});
// 使用索引添加唯一约束
db.ensureIndex({ fieldName: 'somefield', unique: true }, function (err) {
});
// 使用稀疏唯一索引
db.ensureIndex({ fieldName: 'somefield', unique: true, sparse: true }, function (err) {
});
// 当唯一约束未满足时的错误消息格式
db.insert({ somefield: 'nedb' }, function (err) {
// err 为 null
db.insert({ somefield: 'nedb' }, function (err) {
// err 为 { errorType: 'uniqueViolated', key: 'name', message: 'Unique constraint violated for key name' }
});
});
// 移除字段 somefield 上的索引
db.removeIndex('somefield', function (err) {
});
// 使用 expireAfterSeconds 在创建后 1 小时删除文档的示例(db 的 timestampData 选项在此处为 true)
db.ensureIndex({ fieldName: 'createdAt', expireAfterSeconds: 3600 }, function (err) {
});
// 使用该选项设置过期日期
db.ensureIndex({ fieldName: 'expirationDate', expireAfterSeconds: 0 }, function (err) {
// 现在,所有文档将在系统时间达到其 expirationDate 字段中的日期时过期
});