我正在使用MongoDB Atlas云(https://cloud.mongodb.com/)和Mongoose库。
我尝试使用事务处理概念创建多个文档,但是它不起作用。 我没有任何错误。但是,回滚似乎无法正常工作。
app.js
'mat-icon' is not a known element:
1. If 'mat-icon' is an Angular component, then verify that it is part of this module.
2. If 'mat-icon' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("eld>
<input matInput placeholder="Enter your password" [type]="hide ? 'password' : 'text'">
[ERROR ->]<mat-icon matSuffix (click)="hide = !hide">{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
</"): ng:///AppModule/ApplicationComponent.html@4:4
'mat-form-field' is not a known element:
1. If 'mat-form-field' is an Angular component, then verify that it is part of this module.
2. If 'mat-form-field' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("<h1>Products List</h1>
<div class="example-container">
[ERROR ->]<mat-form-field>
<input matInput placeholder="Enter your password" [type]="hide ? 'password' : 't"): ng:///AppModule/ApplicationComponent.html@2:2
at syntaxError (compiler.js:2547)
at TemplateParser.push../node_modules/@angular/compiler/fesm5/compiler.js.TemplateParser.parse (compiler.js:19495)
at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler._parseTemplate (compiler.js:25041)
at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler._compileTemplate (compiler.js:25028)
at compiler.js:24971
at Set.forEach (<anonymous>)
at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler._compileComponents (compiler.js:24971)
at compiler.js:24881
at Object.then (compiler.js:2538)
at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler._compileModuleAndComponents (compiler.js:24880)
models / db.js
//*** more code here
var app = express();
require('./models/db');
//*** more code here
models / user.js
var mongoose = require( 'mongoose' );
// Build the connection string
var dbURI = 'mongodb+srv://mydb:pass@cluster0-****.mongodb.net/mydb?retryWrites=true';
// Create the database connection
mongoose.connect(dbURI, {
useCreateIndex: true,
useNewUrlParser: true,
});
// Get Mongoose to use the global promise library
mongoose.Promise = global.Promise;
myroute.js
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
userName: {
type: String,
required: true
},
pass: {
type: String,
select: false
}
});
module.exports = mongoose.model("User", UserSchema, "user");
以上代码可正常运行,但仍会在数据库中创建用户。它假定要回滚创建的用户,并且集合应该为空。
我不知道我在这里错过了什么。有人可以让我知道这是怎么回事吗?
应用,模型,架构和路由器位于不同的文件中。
答案 0 :(得分:3)
您需要在事务期间处于活动状态的所有读/写操作的选项中包括session
。只有这样,它们才真正应用于您可以回滚它们的事务范围。
作为更完整的清单,只使用更经典的Order/OrderItems
建模,对于具有一定关系交易经验的大多数人来说,它们应该非常熟悉:
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost:27017/trandemo';
const opts = { useNewUrlParser: true };
// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
// schema defs
const orderSchema = new Schema({
name: String
});
const orderItemsSchema = new Schema({
order: { type: Schema.Types.ObjectId, ref: 'Order' },
itemName: String,
price: Number
});
const Order = mongoose.model('Order', orderSchema);
const OrderItems = mongoose.model('OrderItems', orderItemsSchema);
// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));
// main
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)
let session = await conn.startSession();
session.startTransaction();
// Collections must exist in transactions
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.createCollection())
);
let [order, other] = await Order.insertMany([
{ name: 'Bill' },
{ name: 'Ted' }
], { session });
let fred = new Order({ name: 'Fred' });
await fred.save({ session });
let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);
// update an item
let result1 = await OrderItems.updateOne(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ session }
);
log(result1);
// commit
await session.commitTransaction();
// start another
session.startTransaction();
// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);
await session.abortTransaction();
/*
* $lookup join - expect Milk to be price: 4
*
*/
let joined = await Order.aggregate([
{ '$match': { _id: order._id } },
{ '$lookup': {
'from': OrderItems.collection.name,
'foreignField': 'order',
'localField': '_id',
'as': 'orderitems'
}}
]);
log(joined);
} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}
})()
因此,我通常建议使用小写形式调用变量session
,因为这是“选项”对象的键名,在所有操作中都需要它。将其保持为小写约定还允许使用ES6对象分配之类的东西:
const conn = await mongoose.connect(uri, opts);
...
let session = await conn.startSession();
session.startTransaction();
猫鼬documentation on transactions还是有点误导,或者至少可以更具描述性。在示例中,它所指的db
实际上是Mongoose Connection实例,而不是底层的Db
甚至是mongoose
全局导入,因为有些人可能会误解这一点。请注意,清单和上面的摘录中的内容是从mongoose.connect()
获得的,应将其保存在您的代码中,以便您可以从共享导入中访问这些内容。
或者,甚至在建立连接后的任何时间,您都可以通过mongoose.connection
属性以模块化代码的形式获取此信息。在服务器路由处理程序之类的东西内部这通常是安全的,因为在调用代码时将建立数据库连接。
代码还演示了session
在不同模型方法中的用法:
let [order, other] = await Order.insertMany([
{ name: 'Bill' },
{ name: 'Ted' }
], { session });
let fred = new Order({ name: 'Fred' });
await fred.save({ session });
所有基于find()
的方法以及基于update()
或insert()
和delete()
的方法都具有最终的“选项块”,该会话键和值是期望的。 save()
方法的唯一参数是此选项块。这就是告诉MongoDB在引用的会话上将这些操作应用于当前事务的原因。
以几乎相同的方式,在提交事务之前,任何未指定find()
选项的session
或类似请求都不会在该事务进行中看到数据状态。事务完成后,修改后的数据状态仅对其他操作可用。请注意,这会对documentation中所述的写入产生影响。
发出“中止”时:
// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);
await session.abortTransaction();
对活动事务的任何操作都将从状态中删除,并且不应用。因此,它们对于以后的操作不可见。在此处的示例中,文档中的值将递增,并且将在当前会话中显示检索到的值5
。但是,在session.abortTransaction()
之后,将恢复文档的先前状态。请注意,任何未在同一会话上读取数据的全局上下文,除非已提交,否则不会看到状态更改。
这应该给出总体概述。可以添加更多的复杂性来处理不同级别的写入失败和重试,但是文档和许多示例中已经对此进行了广泛介绍,或者可以回答更具体的问题。
作为参考,包含的清单的输出如下所示:
Mongoose: orders.deleteMany({}, {})
Mongoose: orderitems.deleteMany({}, {})
Mongoose: orders.insertMany([ { _id: 5bf775986c7c1a61d12137dd, name: 'Bill', __v: 0 }, { _id: 5bf775986c7c1a61d12137de, name: 'Ted', __v: 0 } ], { session: ClientSession("80f827fe077044c8b6c0547b34605cb2") })
Mongoose: orders.insertOne({ _id: ObjectId("5bf775986c7c1a61d12137df"), name: 'Fred', __v: 0 }, { session: ClientSession("80f827fe077044c8b6c0547b34605cb2") })
Mongoose: orderitems.insertMany([ { _id: 5bf775986c7c1a61d12137e0, order: 5bf775986c7c1a61d12137dd, itemName: 'Cheese', price: 1, __v: 0 }, { _id: 5bf775986c7c1a61d12137e1, order: 5bf775986c7c1a61d12137dd, itemName: 'Bread', price: 2, __v: 0 }, { _id: 5bf775986c7c1a61d12137e2, order: 5bf775986c7c1a61d12137dd, itemName: 'Milk', price: 3, __v: 0 } ], { session: ClientSession("80f827fe077044c8b6c0547b34605cb2") })
Mongoose: orderitems.updateOne({ order: ObjectId("5bf775986c7c1a61d12137dd"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("80f827fe077044c8b6c0547b34605cb2") })
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626894672394452998",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626894672394452998",
"$clusterTime": {
"clusterTime": "6626894672394452998",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: orderitems.findOneAndUpdate({ order: ObjectId("5bf775986c7c1a61d12137dd"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("80f827fe077044c8b6c0547b34605cb2"), upsert: false, remove: false, projection: {}, returnOriginal: false })
{
"_id": "5bf775986c7c1a61d12137e2",
"order": "5bf775986c7c1a61d12137dd",
"itemName": "Milk",
"price": 5,
"__v": 0
}
Mongoose: orders.aggregate([ { '$match': { _id: 5bf775986c7c1a61d12137dd } }, { '$lookup': { from: 'orderitems', foreignField: 'order', localField: '_id', as: 'orderitems' } } ], {})
[
{
"_id": "5bf775986c7c1a61d12137dd",
"name": "Bill",
"__v": 0,
"orderitems": [
{
"_id": "5bf775986c7c1a61d12137e0",
"order": "5bf775986c7c1a61d12137dd",
"itemName": "Cheese",
"price": 1,
"__v": 0
},
{
"_id": "5bf775986c7c1a61d12137e1",
"order": "5bf775986c7c1a61d12137dd",
"itemName": "Bread",
"price": 2,
"__v": 0
},
{
"_id": "5bf775986c7c1a61d12137e2",
"order": "5bf775986c7c1a61d12137dd",
"itemName": "Milk",
"price": 4,
"__v": 0
}
]
}
]