Hướng dẫn embedded collection in mongodb - bộ sưu tập nhúng trong mongodb

1. Embedded Documents là gì?

Embedded documents (Tài liệu nhúng) là documents có lược đồ riêng và là 1 phần của documents khác. Hiểu đơn giản thì embedded documents là 1 field nằm trong 1 collection thay vì lưu dữ liệu kiểu References ta phải thiết kế 2 collection để thể hiện mối quan hệ One-to-Many.

Embedded documents có mọi đặc điểm như 1 model, ta có thể sử dụng validators, middleware,... để xử lý lỗi khi làm việc với kiểu dữ liệu này.

Trong Mongoose, Embedded documents được khai báo dưới dạng array trong collection chứa nó và như nói phía trên, nó sẽ có 1 lược đồ riêng nhưng nằm trong cùng 1 file với collection chính.

Ví dụ: Ta cần quản lý các Students và mỗi students sẽ có nhiều Projects khác nhau. Khi này ta sẽ có 1 file chứa lược đồ như sau: Ta cần quản lý các Students và mỗi students sẽ có nhiều Projects khác nhau. Khi này ta sẽ có 1 file chứa lược đồ như sau:

var Projects = new Schema({
  title: String,
  unit: String
})


var Student = new Schema({
  name: String,
  age: Number,
  projects: [Projects]
})

mongoose.model('Student', Student);

Ta có thể thấy cặp key-value projects: [Projects] trong lược đồ Student, value là 1 array đặc biệt có các phương thức cụ thể để làm việc với Embedded Documents và array này là một thể hiện của DocumentArray. Ở các cặp key-value khác thì value là 1 kiểu dữ liệu được định nghĩa sẵn. Ta cần định nghĩa cho DocumentArray này bằng 1 lược đồ riêng.projects: [Projects] trong lược đồ Student, value là 1 array đặc biệt có các phương thức cụ thể để làm việc với Embedded Documents và array này là một thể hiện của DocumentArray. Ở các cặp key-value khác thì value là 1 kiểu dữ liệu được định nghĩa sẵn. Ta cần định nghĩa cho DocumentArray này bằng 1 lược đồ riêng.

Lưu ý: Lược đồ con luôn phải được xác định trước lược đồ chính.: Lược đồ con luôn phải được xác định trước lược đồ chính.

2. So sánh Embedded Documents vs References

Trước khi so sánh giữa 2 kiểu mô hình dữ liệu thì ta xem qua với ví dụ phía trên khi sử dụng kiểu References thì xây dựng lược đồ như thế nào đã nhé.References thì xây dựng lược đồ như thế nào đã nhé.

Ta có 2 file: Project.js và Student.js

//Project.js
var Project = new Schema({
  title: String,
  unit: String,
  student: { type:Schema.ObjectId, ref: "Student" }
});

mongoose.model('Project', Project);
//Student.js
var Student = new Schema({
  name: String,
  age: Number
});

mongoose.model('Student', Student);

References lưu trữ các mối quan hệ giữa dữ liệu bằng cách link từ collection này sang collection khác (tạo tham chiếu đến thằng cha) thông qua ObjectIds. Ở đây, nếu ta có một bản dữ liệu trong từng Student thì ta lưu ObjectID của Student trên từng Project. Thuộc tính ref phải khớp chính xác với tên model trong định nghĩa model của chúng ta.

Cơ bản thì cách sử dụng 2 mô hình dữ liệu này trong Mongoose là như vậy. Mỗi kiểu có ưu, nhược điểm khác nhau:

Embedded DocumentsReferences
Ưu điểm - Truy vấn và cập nhật dữ liệu dễ dàng.- Đạt hiệu suất cao trong việc đọc dữ liệu
- Đạt hiệu suất cao trong việc đọc dữ liệu
- Có thể cung cấp linh hoạt hơn với truy vấn. - Đạt hiệu suất cao trong việc ghi dữ liệu
- Đạt hiệu suất cao trong việc ghi dữ liệu
Nhược điểm - Kích thước document lớn ảnh hưởng đến việc ghi dữ liệu vì mỗi document không thể vượt quá 16MB - Với các hệ thống có nhiều collections thì truy vấn sẽ khó khăn hơn, yêu cầu nhiều công việc hơn

Bài viết này nói về Embedded Document nên chúng ta sẽ tiếp tục với các thao tác cơ bản của mô hình dữ liệu này.Embedded Document nên chúng ta sẽ tiếp tục với các thao tác cơ bản của mô hình dữ liệu này.

3. Thêm 1 Embedded Document vào 1 mảng

// retrieve my model
var Student = mongoose.model('Student');

// create a student
var student = new Student({ name: 'Cao Thanh Sang', age: 23 });

// create a project
student.projects.push({ title: 'Project 1', unit: 'NodeJs' });

student.save(function (err) {
    if (err) {
        console.log(err);
    } else {
        console.log('Success!');
    }
});

4. Xóa 1 Embedded Document

Student.findById(myId, function (err, student) {
    if (err) {
        console.log(err)
    } else {
        student.projects[0].remove();
        student.save(function (err) {
            // do something
        });
    };
});

5. Tìm Embedded Document theo id

DocumentArrays có method đặc biệt là id() để lọc các Embedded Document theo thuộc tính _id của chúngid() để lọc các Embedded Document theo thuộc tính _id của chúng

student.projects.id(my_id).remove();
student.save(function (err) {
    // embedded project with id `my_id` removed!
});

Bài viết đến đây kết thúc rồi!!! Mình xin cảm ơn các bạn đã đọc bài. Nếu có sai sót hoặc cách diễn đạt của mình sai thì hãy comment phía dưới nhé.

Tài liệu tham khảo:

  1. https://mongoosejs.com/docs/2.7.x/docs/embedded-documents.html
  2. https://docs.mongodb.com/manual/core/data-modeling-introduction/

Tài liệu về nhà → Hướng dẫn sử dụng MongoDBMongoDB Manual

Trang này mô tả một mô hình dữ liệu sử dụng các tài liệu nhúng để mô tả mối quan hệ một-nhiều giữa dữ liệu được kết nối. Nhúng dữ liệu được kết nối vào một tài liệu duy nhất có thể giảm số lượng hoạt động đọc cần thiết để có được dữ liệu. Nói chung, bạn nên cấu trúc lược đồ của mình để ứng dụng của bạn nhận được tất cả thông tin cần thiết trong một thao tác đọc.

Hãy xem xét các ví dụ sau đây ánh xạ người bảo trợ và nhiều mối quan hệ địa chỉ. Ví dụ minh họa lợi thế của việc nhúng trong việc tham khảo nếu bạn cần xem nhiều thực thể dữ liệu trong bối cảnh của một thực thể khác. Trong mối quan hệ một-nhiều này giữa dữ liệu

//Project.js
var Project = new Schema({
  title: String,
  unit: String,
  student: { type:Schema.ObjectId, ref: "Student" }
});

mongoose.model('Project', Project);
1 và
//Project.js
var Project = new Schema({
  title: String,
  unit: String,
  student: { type:Schema.ObjectId, ref: "Student" }
});

mongoose.model('Project', Project);
2,
//Project.js
var Project = new Schema({
  title: String,
  unit: String,
  student: { type:Schema.ObjectId, ref: "Student" }
});

mongoose.model('Project', Project);
1 có nhiều thực thể
//Project.js
var Project = new Schema({
  title: String,
  unit: String,
  student: { type:Schema.ObjectId, ref: "Student" }
});

mongoose.model('Project', Project);
2.

Trong mô hình dữ liệu được chuẩn hóa, các tài liệu

//Project.js
var Project = new Schema({
  title: String,
  unit: String,
  student: { type:Schema.ObjectId, ref: "Student" }
});

mongoose.model('Project', Project);
2 chứa tham chiếu đến tài liệu
//Project.js
var Project = new Schema({
  title: String,
  unit: String,
  student: { type:Schema.ObjectId, ref: "Student" }
});

mongoose.model('Project', Project);
1.

// patron document
{
_id: "joe",
name: "Joe Bookreader"
}
// address documents
{
patron_id: "joe", // reference to patron document
street: "123 Fake Street",
city: "Faketon",
state: "MA",
zip: "12345"
}
{
patron_id: "joe",
street: "1 Some Other Street",
city: "Boston",
state: "MA",
zip: "12345"
}

Nếu ứng dụng của bạn thường xuyên truy xuất dữ liệu

//Project.js
var Project = new Schema({
  title: String,
  unit: String,
  student: { type:Schema.ObjectId, ref: "Student" }
});

mongoose.model('Project', Project);
2 với thông tin
//Project.js
var Project = new Schema({
  title: String,
  unit: String,
  student: { type:Schema.ObjectId, ref: "Student" }
});

mongoose.model('Project', Project);
8, thì ứng dụng của bạn cần phát hành nhiều truy vấn để giải quyết các tài liệu tham khảo. Một lược đồ tối ưu hơn sẽ là nhúng các thực thể dữ liệu
//Project.js
var Project = new Schema({
  title: String,
  unit: String,
  student: { type:Schema.ObjectId, ref: "Student" }
});

mongoose.model('Project', Project);
2 vào dữ liệu
//Project.js
var Project = new Schema({
  title: String,
  unit: String,
  student: { type:Schema.ObjectId, ref: "Student" }
});

mongoose.model('Project', Project);
1, như trong tài liệu sau:

{
"_id": "joe",
"name": "Joe Bookreader",
"addresses": [
{
"street": "123 Fake Street",
"city": "Faketon",
"state": "MA",
"zip": "12345"
},
{
"street": "1 Some Other Street",
"city": "Boston",
"state": "MA",
"zip": "12345"
}
]
}

Với mô hình dữ liệu được nhúng, ứng dụng của bạn có thể lấy thông tin bảo trợ hoàn chỉnh bằng một truy vấn.

Một vấn đề tiềm năng với mẫu tài liệu nhúng là nó có thể dẫn đến các tài liệu lớn, đặc biệt là nếu trường nhúng không bị ràng buộc. Trong trường hợp này, bạn có thể sử dụng mẫu tập hợp con để chỉ truy cập dữ liệu được yêu cầu bởi ứng dụng, thay vì toàn bộ tập hợp dữ liệu được nhúng.embedded document pattern is that it can lead to large documents, especially if the embedded field is unbounded. In this case, you can use the subset pattern to only access data which is required by the application, instead of the entire set of embedded data.

Xem xét một trang web thương mại điện tử có danh sách các đánh giá cho một sản phẩm:

{
"_id": 1,
"name": "Super Widget",
"description": "This is the most useful item in your toolbox.",
"price": { "value": NumberDecimal("119.99"), "currency": "USD" },
"reviews": [
{
"review_id": 786,
"review_author": "Kristina",
"review_text": "This is indeed an amazing widget.",
"published_date": ISODate("2019-02-18")
},
{
"review_id": 785,
"review_author": "Trina",
"review_text": "Nice product. Slow shipping.",
"published_date": ISODate("2019-02-17")
},
...
{
"review_id": 1,
"review_author": "Hans",
"review_text": "Meh, it's okay.",
"published_date": ISODate("2017-12-06")
}
]
}

Các đánh giá được sắp xếp theo thứ tự thời gian ngược. Khi người dùng truy cập trang sản phẩm, ứng dụng sẽ tải mười đánh giá gần đây nhất.

Thay vì lưu trữ tất cả các đánh giá với sản phẩm, bạn có thể chia bộ sưu tập thành hai bộ sưu tập:

  • Bộ sưu tập

    //Student.js
    var Student = new Schema({
      name: String,
      age: Number
    });
    
    mongoose.model('Student', Student);
    
    1 lưu trữ thông tin về từng sản phẩm, bao gồm mười đánh giá gần đây nhất của sản phẩm:

    {
    "_id": 1,
    "name": "Super Widget",
    "description": "This is the most useful item in your toolbox.",
    "price": { "value": NumberDecimal("119.99"), "currency": "USD" },
    "reviews": [
    {
    "review_id": 786,
    "review_author": "Kristina",
    "review_text": "This is indeed an amazing widget.",
    "published_date": ISODate("2019-02-18")
    }
    ...
    {
    "review_id": 777,
    "review_author": "Pablo",
    "review_text": "Amazing!",
    "published_date": ISODate("2019-02-16")
    }
    ]
    }

  • Bộ sưu tập

    //Student.js
    var Student = new Schema({
      name: String,
      age: Number
    });
    
    mongoose.model('Student', Student);
    
    2 lưu trữ tất cả các đánh giá. Mỗi đánh giá chứa một tham chiếu đến sản phẩm mà nó được viết.

    //Project.js
    var Project = new Schema({
      title: String,
      unit: String,
      student: { type:Schema.ObjectId, ref: "Student" }
    });
    
    mongoose.model('Project', Project);
    
    0

Bằng cách lưu trữ mười đánh giá gần đây nhất trong bộ sưu tập

//Student.js
var Student = new Schema({
  name: String,
  age: Number
});

mongoose.model('Student', Student);
1, chỉ có tập hợp con yêu cầu của dữ liệu tổng thể được trả về trong cuộc gọi cho bộ sưu tập
//Student.js
var Student = new Schema({
  name: String,
  age: Number
});

mongoose.model('Student', Student);
1. Nếu người dùng muốn xem các đánh giá bổ sung, ứng dụng gọi điện đến bộ sưu tập
//Student.js
var Student = new Schema({
  name: String,
  age: Number
});

mongoose.model('Student', Student);
2.

Mẹo

Khi xem xét nơi phân chia dữ liệu của bạn, phần dữ liệu được truy cập thường xuyên nhất trong bộ sưu tập mà ứng dụng tải trước. Trong ví dụ này, lược đồ được chia ở mười đánh giá vì đó là số lượng đánh giá có thể hiển thị trong ứng dụng theo mặc định.

Mẹo

Khi xem xét nơi phân chia dữ liệu của bạn, phần dữ liệu được truy cập thường xuyên nhất trong bộ sưu tập mà ứng dụng tải trước. Trong ví dụ này, lược đồ được chia ở mười đánh giá vì đó là số lượng đánh giá có thể hiển thị trong ứng dụng theo mặc định.

Xem thêm:

Sử dụng các tài liệu nhỏ hơn chứa dữ liệu được truy cập thường xuyên hơn làm giảm kích thước tổng thể của bộ làm việc. Các tài liệu nhỏ hơn này dẫn đến hiệu suất đọc được cải thiện cho dữ liệu mà ứng dụng truy cập thường xuyên nhất.

Bạn cũng phải triển khai logic trong ứng dụng của mình để đảm bảo rằng các đánh giá trong bộ sưu tập

//Student.js
var Student = new Schema({
  name: String,
  age: Number
});

mongoose.model('Student', Student);
1 luôn là mười đánh giá gần đây nhất cho sản phẩm đó.

Ngoài các đánh giá sản phẩm, mẫu tập hợp con cũng có thể phù hợp để lưu trữ:

  • Nhận xét trên một bài đăng trên blog, khi bạn chỉ muốn hiển thị các nhận xét gần đây nhất hoặc được đánh giá cao nhất theo mặc định.

  • Các thành viên diễn viên trong một bộ phim, khi bạn chỉ muốn thể hiện các thành viên diễn viên với các vai trò lớn nhất theo mặc định.