从 4.x 迁移到 5.x
请注意:我们计划在 2024 年 3 月 1 日停止对 Mongoose 5 的支持。请参阅我们的 版本支持指南。
从 Mongoose 4.x 迁移到 Mongoose 5.x 时,您应该注意一些 向后不兼容的更改。
如果您仍然使用 Mongoose 3.x,请阅读 Mongoose 3.x 到 4.x 的迁移指南。
- 版本要求
- 查询中间件
mongoose.connect()
的承诺和回调- 连接逻辑和
useMongoClient
- 设置器顺序
- 检查路径是否已填充
remove()
和deleteX()
的返回值- 聚合游标
- geoNear
- 连接字符串的必需 URI 编码
- 包含某些字符的密码
- 域套接字
toObject()
选项- 聚合参数
- 布尔值转换
- 查询转换
- 保存后钩子获取流程控制
$pushAll
运算符- 始终使用前向键顺序
- 在查询上运行设置器
- 预编译的浏览器捆绑包
- 保存错误
- 初始化钩子签名
numAffected
和save()
remove()
和去抖动getPromiseConstructor()
- 从预钩子传递参数
- 数组的
required
验证器 - 调试输出默认使用标准输出而不是标准错误
- 覆盖过滤器属性
bulkWrite()
结果- 严格的 SSL 验证
版本要求
Mongoose 现在需要 Node.js >= 4.0.0 和 MongoDB >= 3.0.0。 MongoDB 2.6 和 Node.js < 4 在 2016 年均已停止支持。
查询中间件
查询中间件现在在您调用 mongoose.model()
或 db.model()
时进行编译。如果您在调用 mongoose.model()
后添加查询中间件,该中间件将不会被调用。
const schema = new Schema({ name: String });
const MyModel = mongoose.model('Test', schema);
schema.pre('find', () => { console.log('find!'); });
MyModel.find().exec(function() {
// In mongoose 4.x, the above `.find()` will print "find!"
// In mongoose 5.x, "find!" will **not** be printed.
// Call `pre('find')` **before** calling `mongoose.model()` to make the middleware apply.
});
mongoose.connect()
的承诺和回调
mongoose.connect()
和 mongoose.disconnect()
现在如果未指定回调则返回一个承诺,否则返回 null
。它不会返回 mongoose 单例。
// Worked in mongoose 4. Does **not** work in mongoose 5, `mongoose.connect()`
// now returns a promise consistently. This is to avoid the horrible things
// we've done to allow mongoose to be a thenable that resolves to itself.
mongoose.connect('mongodb://127.0.0.1:27017/test').model('Test', new Schema({}));
// Do this instead
mongoose.connect('mongodb://127.0.0.1:27017/test');
mongoose.model('Test', new Schema({}));
连接逻辑和 useMongoClient
在 Mongoose 5 中,useMongoClient
选项 已被删除,现在始终为 true
。因此,Mongoose 5 不再支持 mongoose.connect()
的几个函数签名,这些签名在 Mongoose 4.x 中如果 useMongoClient
选项关闭则有效。以下是 mongoose.connect()
调用的一些示例,这些示例在 Mongoose 5.x 中无效。
mongoose.connect('127.0.0.1', 27017);
mongoose.connect('127.0.0.1', 'mydb', 27017);
mongoose.connect('mongodb://host1:27017,mongodb://host2:27017');
在 Mongoose 5.x 中,如果指定,mongoose.connect()
和 mongoose.createConnection()
的第一个参数必须是 MongoDB 连接字符串。然后,连接字符串和选项将传递到 MongoDB Node.js 驱动程序的 MongoClient.connect()
函数。Mongoose 不会修改连接字符串,尽管 mongoose.connect()
和 mongoose.createConnection()
支持 除 MongoDB 驱动程序支持的选项之外的几个附加选项。
设置器顺序
设置器在 4.x 中以相反的顺序运行
const schema = new Schema({ name: String });
schema.path('name').
set(() => console.log('This will print 2nd')).
set(() => console.log('This will print first'));
在 5.x 中,设置器按照声明的顺序运行。
const schema = new Schema({ name: String });
schema.path('name').
set(() => console.log('This will print first')).
set(() => console.log('This will print 2nd'));
检查路径是否已填充
Mongoose 5.1.0 为 ObjectId 引入了一个 _id
getter,它允许您无论路径是否已填充,都可以获取 ObjectId。
const blogPostSchema = new Schema({
title: String,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Author'
}
});
const BlogPost = mongoose.model('BlogPost', blogPostSchema);
await BlogPost.create({ title: 'test', author: author._id });
const blogPost = await BlogPost.findOne();
console.log(blogPost.author); // '5b207f84e8061d1d2711b421'
// New in Mongoose 5.1.0: this will print '5b207f84e8061d1d2711b421' as well
console.log(blogPost.author._id);
await blogPost.populate('author');
console.log(blogPost.author._id); // '5b207f84e8061d1d2711b421'
因此,检查 blogPost.author._id
是否 不再是检查 author
是否已填充的可行方法。使用 blogPost.populated('author') != null
或 blogPost.author instanceof mongoose.Types.ObjectId
来检查 author
是否已填充。
请注意,您可以调用 mongoose.set('objectIdGetter', false)
来更改此行为。
remove()
和 deleteX()
的返回值
deleteOne()
、deleteMany()
和 remove()
现在解析为结果对象,而不是完整的 驱动程序 WriteOpResult
对象。
// In 4.x, this is how you got the number of documents deleted
MyModel.deleteMany().then(res => console.log(res.result.n));
// In 5.x this is how you get the number of documents deleted
MyModel.deleteMany().then(res => res.n);
聚合游标
4.x 中的 useMongooseAggCursor
选项现在始终处于开启状态。这是 mongoose 5 中聚合游标的新语法
// When you call `.cursor()`, `.exec()` will now return a mongoose aggregation
// cursor.
const cursor = MyModel.aggregate([{ $match: { name: 'Val' } }]).cursor().exec();
// No need to `await` on the cursor or wait for a promise to resolve
cursor.eachAsync(doc => console.log(doc));
// Can also pass options to `cursor()`
const cursorWithOptions = MyModel.
aggregate([{ $match: { name: 'Val' } }]).
cursor({ batchSize: 10 }).
exec();
geoNear
Model.geoNear()
已被删除,因为 MongoDB 驱动程序不再支持它
连接字符串的必需 URI 编码
由于 MongoDB 驱动程序的更改,连接字符串必须进行 URI 编码。
如果未编码,连接可能会因非法字符消息而失败。
包含某些字符的密码
查看 受影响字符的完整列表。
如果您的应用程序被许多不同的连接字符串使用,则测试用例可能会通过,但生产密码可能会失败。对所有连接字符串进行编码以确保安全。
如果您想继续使用未编码的连接字符串,最简单的解决方法是使用 mongodb-uri
模块来解析连接字符串,然后生成正确编码的版本。您可以使用以下函数
const uriFormat = require('mongodb-uri');
function encodeMongoURI(urlString) {
if (urlString) {
const parsed = uriFormat.parse(urlString);
urlString = uriFormat.format(parsed);
}
return urlString;
}
// Your un-encoded string.
const mongodbConnectString = 'mongodb://...';
mongoose.connect(encodeMongoURI(mongodbConnectString));
以上函数无论现有字符串是否已编码都可以安全使用。
域套接字
域套接字必须进行 URI 编码。例如
// Works in mongoose 4. Does **not** work in mongoose 5 because of more
// stringent URI parsing.
const host = '/tmp/mongodb-27017.sock';
mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`);
// Do this instead
const host = encodeURIComponent('/tmp/mongodb-27017.sock');
mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`);
toObject()
选项
toObject()
和 toJSON()
的 options
参数合并默认值,而不是覆盖它们。
// Note the `toObject` option below
const schema = new Schema({ name: String }, { toObject: { virtuals: true } });
schema.virtual('answer').get(() => 42);
const MyModel = db.model('MyModel', schema);
const doc = new MyModel({ name: 'test' });
// In mongoose 4.x this prints "undefined", because `{ minimize: false }`
// overwrites the entire schema-defined options object.
// In mongoose 5.x this prints "42", because `{ minimize: false }` gets
// merged with the schema-defined options.
console.log(doc.toJSON({ minimize: false }).answer);
聚合参数
aggregate()
不再接受展开,您必须将聚合管道作为数组传递。以下代码在 4.x 中有效
MyModel.aggregate({ $match: { isDeleted: false } }, { $skip: 10 }).exec(cb);
以上代码在 5.x 中无效,您必须将 $match
和 $skip
阶段包装在数组中。
MyModel.aggregate([{ $match: { isDeleted: false } }, { $skip: 10 }]).exec(cb);
布尔值转换
默认情况下,mongoose 4 会将任何值强制转换为布尔值而不会报错。
// Fine in mongoose 4, would save a doc with `boolField = true`
const MyModel = mongoose.model('Test', new Schema({
boolField: Boolean
}));
MyModel.create({ boolField: 'not a boolean' });
Mongoose 5 只会将以下值转换为 true
true
'true'
1
'1'
'yes'
并将以下值转换为 false
false
'false'
0
'0'
'no'
所有其他值都会导致 CastError
查询转换
update()
、updateOne()
、updateMany()
、replaceOne()
、remove()
、deleteOne()
和 deleteMany()
的转换直到 exec()
才会发生。这使得钩子和自定义查询助手更容易修改数据,因为 mongoose 不会在您的钩子和查询助手运行后才重新构造您传递的数据。它还使得在传递更新后设置overwrite
选项成为可能。
// In mongoose 4.x, this becomes `{ $set: { name: 'Baz' } }` despite the `overwrite`
// In mongoose 5.x, this overwrite is respected and the first document with
// `name = 'Bar'` will be replaced with `{ name: 'Baz' }`
User.where({ name: 'Bar' }).update({ name: 'Baz' }).setOptions({ overwrite: true });
保存后钩子获取流程控制
后钩子现在获得流程控制,这意味着异步保存后钩子和子文档保存后钩子在您的 save()
回调之前执行。
const ChildModelSchema = new mongoose.Schema({
text: {
type: String
}
});
ChildModelSchema.post('save', function(doc) {
// In mongoose 5.x this will print **before** the `console.log()`
// in the `save()` callback. In mongoose 4.x this was reversed.
console.log('Child post save');
});
const ParentModelSchema = new mongoose.Schema({
children: [ChildModelSchema]
});
const Model = mongoose.model('Parent', ParentModelSchema);
const m = new Model({ children: [{ text: 'test' }] });
m.save(function() {
// In mongoose 5.xm this prints **after** the "Child post save" message.
console.log('Save callback');
});
$pushAll
运算符
$pushAll
不再受支持,也不再用于 save()
的内部,因为它已 自 MongoDB 2.4 起弃用。请改用 $push
和 $each
。
始终使用前向键顺序
retainKeyOrder
选项已被删除,mongoose 现在将始终在克隆对象时保留相同的键位置。如果您有依赖于反向键顺序的查询或索引,则需要更改它们。
在查询上运行设置器
设置器现在默认情况下在查询上运行,并且已删除旧的 runSettersOnQuery
选项。
const schema = new Schema({
email: { type: String, lowercase: true }
});
const Model = mongoose.model('Test', schema);
Model.find({ email: '[email protected]' }); // Converted to `find({ email: '[email protected]' })`
预编译的浏览器捆绑包
我们不再为浏览器提供预编译版本的 mongoose。如果您想在浏览器中使用 mongoose 模式,您需要使用 browserify/webpack 构建自己的捆绑包。
保存错误
saveErrorIfNotFound
选项已被删除,mongoose 现在如果底层文档未找到,则始终会从 save()
中出错
初始化钩子签名
init
钩子现在完全同步,并且不会将 next()
作为参数接收。
Document.prototype.init()
不再将回调作为参数接收。它始终是同步的,只是出于遗留原因才具有回调。
numAffected
和 save()
doc.save()
不再将 numAffected
作为第三个参数传递给其回调。
remove()
和去抖动
doc.remove()
不再进行去抖动
getPromiseConstructor()
getPromiseConstructor()
已消失,直接使用 mongoose.Promise
即可。
从预钩子传递参数
您不能在 mongoose 5.x 中使用 next()
在链中将参数传递给下一个预中间件。在 mongoose 4 中,预中间件中的 next('Test')
会使用 'Test' 作为参数调用下一个中间件。Mongoose 5.x 已删除对该功能的支持。
数组的 required
验证器
在 mongoose 5 中,required
验证器只验证值是否为数组。也就是说,它不会像 mongoose 4 那样对空数组失败。
调试输出默认使用标准输出而不是标准错误
在 mongoose 5 中,默认调试函数使用 console.info()
来显示消息,而不是 console.error()
。
覆盖过滤器属性
在 Mongoose 4.x 中,用对象覆盖作为原语的过滤器属性会默默失败。例如,以下代码将忽略 where()
,等效于 Sport.find({ name: 'baseball' })
Sport.find({ name: 'baseball' }).where({ name: { $ne: 'softball' } });
在 Mongoose 5.x 中,以上代码将正确地用 { $ne: 'softball' }
覆盖 'baseball'
bulkWrite()
结果
Mongoose 5.x 使用 MongoDB Node.js 驱动程序 的 3.x 版本。MongoDB 驱动程序 3.x 更改了 bulkWrite()
调用 结果的格式,因此不再存在顶层的 nInserted
、nModified
等属性。新的结果对象结构 在此处描述。
const Model = mongoose.model('Test', new Schema({ name: String }));
const res = await Model.bulkWrite([{ insertOne: { document: { name: 'test' } } }]);
console.log(res);
在 Mongoose 4.x 中,以上将打印
BulkWriteResult {
ok: [Getter],
nInserted: [Getter],
nUpserted: [Getter],
nMatched: [Getter],
nModified: [Getter],
nRemoved: [Getter],
getInsertedIds: [Function],
getUpsertedIds: [Function],
getUpsertedIdAt: [Function],
getRawResponse: [Function],
hasWriteErrors: [Function],
getWriteErrorCount: [Function],
getWriteErrorAt: [Function],
getWriteErrors: [Function],
getLastOp: [Function],
getWriteConcernError: [Function],
toJSON: [Function],
toString: [Function],
isOk: [Function],
insertedCount: 1,
matchedCount: 0,
modifiedCount: 0,
deletedCount: 0,
upsertedCount: 0,
upsertedIds: {},
insertedIds: { '0': 5be9a3101638a066702a0d38 },
n: 1 }
在 Mongoose 5.x 中,脚本将打印
BulkWriteResult {
result:
{ ok: 1,
writeErrors: [],
writeConcernErrors: [],
insertedIds: [ [Object] ],
nInserted: 1,
nUpserted: 0,
nMatched: 0,
nModified: 0,
nRemoved: 0,
upserted: [],
lastOp: { ts: [Object], t: 1 } },
insertedCount: 1,
matchedCount: 0,
modifiedCount: 0,
deletedCount: 0,
upsertedCount: 0,
upsertedIds: {},
insertedIds: { '0': 5be9a1c87decfc6443dd9f18 },
n: 1 }
严格的 SSL 验证
MongoDB Node.js 驱动程序的最新版本默认使用严格的 SSL 验证,如果您使用 自签名证书,这可能会导致错误。
如果这阻止您升级,您可以将 tlsInsecure
选项设置为 true
。
mongoose.connect(uri, { tlsInsecure: false }); // Opt out of additional SSL validation