假设我有一组文档,例如:
{ "_id" : 0 , "owner":0 "name":"Doc1"},{ "_id" : 1 , "owner":1, "name":"Doc1"}, etc
另一方面,所有者被表示为一个单独的集合:
{ "_id" : 0 , "username":"John"}, { "_id" : 1 , "username":"Sam"}
如何确保当我插入文档时它以正确的方式引用用户。在老式 RDBMS 中,这可以使用外键轻松完成。
我知道我可以从我的业务代码中检查插入的正确性,但是如果攻击者篡改我对服务器的请求并将“owner”:100,并且Mongo不会抛出任何异常怎么办?
我想知道在实际应用中应该如何处理这种情况。
提前谢谢您!
MongoDB 没有外键(您可能已经注意到了)。因此,从根本上来说,答案是“不要让用户篡改请求。只让应用程序插入遵循引用完整性规则的数据。”
MongoDB 在很多方面都很棒...但如果您发现需要外键,那么它可能不是解决您问题的正确解决方案。
为了回答您的具体问题 - 虽然 MongoDB 鼓励在客户端处理外键关系,但它们还提供了“数据库引用”的想法 - 请参阅此帮助页面。
也就是说,我不建议使用 DBRef。要么让您的客户端代码管理关联,要么(更好)从一开始就将文档链接在一起。您可能需要考虑将所有者的“文档”嵌入到所有者对象本身中。组装您的文档以匹配您的使用模式,MongoDB 将大放异彩。
这是一对一的关系。最好将一个文档嵌入到另一个文档中,而不是维护单独的集合。请查看 here 了解如何在 mongodb 中对它们进行建模及其优点。
虽然文档中没有明确提及,但嵌入可以提供与外键约束相同的效果。只是想把这个想法说清楚。当你有两个这样的集合时:
C1:
{ "_id" : 0 , "owner":0 "name":"Doc1"},{ "_id" : 1 , "owner":1, "name":"Doc1"}, etc
C2:
{ "_id" : 0 , "username":"John"}, { "_id" : 1 , "username":"Sam"}
如果您要在
C2._id
上声明外键约束以引用 C1._id
(假设 MongoDB 允许),则意味着您无法将文档插入 C2
,其中 C2._id
在 C1
中不存在
。将此与嵌入文档进行比较:
{
"_id" : 0 ,
"owner" : 0,
"name" : "Doc1",
"owner_details" : {
"username" : "John"
}
}
现在owner_details字段代表来自C2集合的数据,其余字段代表来自C1的数据。您无法将owner_details 字段添加到不存在的文档中。您基本上达到了相同的效果。
这个问题最初是在 2011 年回答的,所以我决定在这里发布更新。
从 MongoDB 4.0 版本(2018 年 6 月发布)开始,开始支持多文档 ACID 事务。
关系现在可以通过两种方法建模:
您可以像这样对引用关系进行建模:
{
"_id":ObjectId("52ffc33cd85242f436000001"),
"contact": "987654321",
"dob": "01-01-1991",
"name": "Tom Benzamin",
"address_ids": [
ObjectId("52ffc4a5d85242602e000000")
]
}
地址文档的示例文档结构:
{
"_id":ObjectId("52ffc4a5d85242602e000000"),
"building": "22 A, Indiana Apt",
"pincode": 123456,
"city": "Los Angeles",
"state": "California"
}
如果有人真的想在项目/WebApp 中强制执行外键。那么你应该使用 MixSQL 方法,即 SQL + NoSQL
我更希望没有太多引用的大数据可以存储在NoSQL数据库存储中。喜欢:酒店或地点类型的数据。
但是如果有一些严重的事情,比如 OAuth 模块表、TokenStore 和 UserDetails 以及 UserRole(映射表)等......那么你可以使用 SQL。
如果您使用 mongoose,则可以在保存模型实例时在 mongoose 的
pre
钩子中编写自己的自定义引用完整性检查。
const schema = new Schema({ /* ... */ });
schema.pre('save', function(next) {
// your own custom referential integrity check
// you can throw an exception if the check fails
next();
});
我还建议如果用户名是唯一的,则使用它们作为 _id。您将节省索引。在存储的文档中,在创建文档时将应用程序中“所有者”的值设置为“用户名”的值,并且永远不要让任何其他代码片段更新它。
如果需要更改所有者,则提供适当的 API 并实施业务规则。
不需要外键。