gpt4 book ai didi

javascript - 如何使用Mongoose使用MongoDB事务?

转载 作者:太空宇宙 更新时间:2023-11-04 01:31:54 26 4
gpt4 key购买 nike

我正在使用 MongoDB Atlas 云( https://cloud.mongodb.com/ ) 和 Mongoose 库。

我尝试使用事务概念创建多个文档,但它不起作用。我没有收到任何错误。但是,回滚似乎无法正常工作。

app.js

//*** more code here

var app = express();

require('./models/db');

//*** more code here

models/db.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;

models/user.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");

myroute.js

const db = require("mongoose");
const User = require("./models/user");

router.post("/addusers", async (req, res, next) => {

const SESSION = await db.startSession();

await SESSION.startTransaction();

try {

const newUser = new User({
//*** data for user ***
});
await newUser.save();

//*** for test purpose, trigger some error ***
throw new Error("some error");

await SESSION.commitTransaction();

//*** return data

} catch (error) {
await SESSION.abortTransaction();
} finally {
SESSION.endSession();
}

});

上面的代码工作没有错误,但它仍然在数据库中创建用户。它假设回滚创建的用户并且集合应该为空。

我不知道我在这里错过了什么。谁能告诉我这里出了什么问题吗?

应用程序、模型、架构和路由器位于不同的文件中。

最佳答案

您需要在事务期间处于事件状态的所有读/写操作的选项中包含 session 。只有这样它们才会真正应用于事务范围,您可以在其中回滚它们。

作为一个更完整的列表,并且仅使用更经典的 Order/OrderItems 建模,对于大多数具有一定关系事务经验的人来说应该非常熟悉:

const { Schema } = mongoose = require('mongoose');

// URI including the name of the replicaSet connecting to
const uri = 'mongodb://localhost:27017/trandemo?replicaSet=fresh';
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();

还有 Mongoose documentation on transactions有点误导,或者至少可以更具描述性。示例中所指的 db 实际上是 Mongoose Connection实例,而不是底层的 Db 甚至 mongoose 全局导入,因为有些人可能会误解这一点。请注意,在 list 和上面的摘录中,这是从 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() 的方法都有一个最终的“选项 block ”,其中需要此 session 键和值。 save() 方法的唯一参数是此选项 block 。这就是告诉 MongoDB 将这些操作应用于该引用 session 上的当前事务。

以同样的方式,在提交事务之前,任何对 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();

事件事务上的任何操作都会从状态中删除并且不会应用。因此,它们对于之后的结果操作是不可见的。在此示例中,文档中的值会递增,并将在当前 session 中显示检索到的值 5。然而,在 session.abortTransaction() 之后,文档的先前状态将被恢复。请注意,任何未在同一 session 上读取数据的全局上下文都不会看到状态更改,除非已提交。

这应该给出总体概述。可以添加更多的复杂性来处理不同级别的写入失败和重试,但这已经在文档和许多示例中广泛涵盖,或者可以回答更具体的问题。

<小时/>

输出

作为引用,所包含列表的输出如下所示:

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
}
]
}
]

关于javascript - 如何使用Mongoose使用MongoDB事务?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55841995/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com