Với ReactJS việc với MongoDB ban đầu rất đơn giản, tuy nhiên khi đi vào các dự án thực tế thì lại khác, lúc này chúng ta lại phải đối diện với những vấn đề không khác khi chúng ta sử dụng SQL là mấy, trong bài này chúng ta sẽ cùng nhau tìm hiểu nhé.

Sử dụng bảng trung gian hay không?

Giả sử bạn đang làm dự án quản lý học sinh, lúc này một học sinh có thể học nhiều lớp, mỗi lớp lại có thể có nhiều học sinh, bạn sẽ làm thế nào? Bạn sẽ lưu thành 2 collection là users và classes kiểu thế này?

[
  {
    "name": "Nguyen Van A",
    "age": 16,
    "classes": [
      {
        "className": "Math",
        "teacher": "Mr. Tuan"
      },
      {
        "className": "Physics",
        "teacher": "Ms. Hoa"
      }
    ]
  },
  {
    "name": "Tran Thi B",
    "age": 17,
    "classes": [
      {
        "className": "Chemistry",
        "teacher": "Mr. Phuc"
      },
      {
        "className": "Biology",
        "teacher": "Ms. Lan"
      }
    ]
  },
  {
    "name": "Le Van C",
    "age": 16,
    "classes": [
      {
        "className": "English",
        "teacher": "Ms. Thao"
      },
      {
        "className": "History",
        "teacher": "Mr. Minh"
      }
    ]
  }
]
[
  {
    "className": "Math",
    "teacher": "Mr. Tuan",
    "students": [
      {
        "name": "Nguyen Van A",
        "age": 16
      },
      {
        "name": "Tran Thi B",
        "age": 17
      },
      {
        "name": "Le Van C",
        "age": 16
      }
    ]
  },
  {
    "className": "Physics",
    "teacher": "Ms. Hoa",
    "students": [
      {
        "name": "Nguyen Van A",
        "age": 16
      },
      {
        "name": "Tran Thi B",
        "age": 17
      },
      {
        "name": "Le Van C",
        "age": 16
      }
    ]
  }
]

Hay bạn sẽ tạo ra một collection trung gian user_classes kiểu thế này?

[
  {
    "className": "Math",
    "studentName": "Nguyen Van A"
  },
  {
    "className": "Physics",
    "studentName": "Nguyen Van A"
  },
  {
    "className": "Math",
    "studentName": "Tran Thi B"
  },
  {
    "className": "Physics",
    "studentName": "Le Van C"
  }
]

Cái này sẽ còn tuỳ vào nghiệp vụ của bạn, giả sử trong một lớp có hàng nghìn học sinh, việc mỗi lần bạn lấy thông tin một lớp, bạn phải lấy ra toàn bộ thông tin học sinh sẽ rất nặng nề, lúc đó bạn sẽ phải nghĩ đến việc sử dụng phân trang và việc sử dụng collection trung gian user_classes sẽ là một lựa chọn phù hợp để tránh làm cho dữ liệu bị lưu trùng nhau ở nhiều nơi để tiết kiệm bộ nhớ, đồng thời chủ động trong nghiệp vụ phân trang.

Sử ObjectId hay id kiểu long tăng dần?

ObjectId là một kiểu dữ liệu đặc biệt do MongoDB sinh ra để làm id cho một bản ghi. ObjectId 12 byte bao gồm:

  • Một dấu thời gian 4 byte, đại diện cho thời điểm tạo ObjectId, được đo bằng giây kể từ Unix epoch.
  • Một giá trị ngẫu nhiên 5 byte được tạo ra một lần cho mỗi tiến trình. Giá trị ngẫu nhiên này là duy nhất cho machine và process.
  • Một bộ đếm tăng dần 3 byte, được khởi tạo bằng một giá trị ngẫu nhiên.
    Mặc dù định dạng BSON bản thân là little-endian, nhưng các giá trị dấu thời gian và bộ đếm lại theo định dạng big-endian, trong đó các byte có giá trị lớn nhất xuất hiện trước trong chuỗi byte.
    Nếu một giá trị số nguyên được sử dụng để tạo ObjectId, giá trị số nguyên sẽ thay thế cho dấu thời gian.
    ObjectId() có thể chấp nhận một trong những đầu vào sau:
  • Hexadecimal: Tuỳ chọn. Một chuỗi giá trị hệ thập lục phân 24 ký tự cho ObjectId mới.
  • Integer: Tuỳ chọn. Giá trị số nguyên, tính bằng giây, được cộng vào Unix epoch để tạo dấu thời gian mới.
    Tham khảo: https://www.mongodb.com/docs/v5.2/reference/method/ObjectId/
    Tuy nhiên hầu hết các hệ quản trị cơ sở dữ liệu SQL lại sử dụng id dạng số nguyên tăng dần.
    Câu hỏi đặt ra là giả sử chúng ta đến một ngày nào đó muốn chuyển đổi dữ liệu sang SQL, hoặc là gửi dữ liệu sang một hệ thống sử dụng SQL thì sao? Lúc đó chúng ta sẽ cần suy nghĩ đến việc sử dụng luôn id của bản ghi trong MongoDB hoặc là tạo thêm 1 trường tên là object_id để ánh xạ với bản ghi trong MongoDB. Cũng có một cách nữa là chúng ta sử dụng 1 collection chuyên quản lý id tăng dần để cấp id cho bản ghi kiểu thế này:
db.id_provider.updateOne(
{ _id: "users" },
{ $inc: { maxId: 1 }
}

Từ câu lệnh update $inc này bạn sẽ làm tăng giá trị maxId lên 1 đơn vị và có thể sử dụng kết quả trả về làm giá trị cho bản ghi user mới. Cá nhân mình thích sử dụng id kiểu long hơn vì nó dễ nhìn hơn khi truy vấn, so sánh và đồng bộ với hệ quản trị cơ sở dữ liệu khác.

Làm sao để join các collection?

Với SQL chúng ta có thể join các bảng tương đối đơn giản kiểu thế này:

SELECT 
    u.user_id,
    u.username,
    o.order_id,
    o.order_date,
    p.product_name,
    p.price
FROM users u
JOIN orders o ON u.user_id = o.user_id
JOIN products p ON o.product_id = p.product_id
WHERE u.user_id = 1; 

Tuy nhiên với mongodb chúng ta sẽ thấy cực kỳ phức tạp như sau:

db.users.aggregate([
  {
    $lookup: {
      from: "orders",             // Tên collection thứ 2
      localField: "user_id",       // Trường trong collection users
      foreignField: "user_id",     // Trường trong collection orders
      as: "user_orders"            // Tên mảng chứa kết quả của join
    }
  },
  {
    $unwind: "$user_orders"        // Tách mảng kết quả join ra thành các document riêng biệt
  },
  {
    $lookup: {
      from: "products",            // Tên collection thứ 3
      localField: "user_orders.product_id", // Trường từ kết quả join trước
      foreignField: "product_id",  // Trường trong collection products
      as: "order_products"         // Tên mảng chứa kết quả của join
    }
  },
  {
    $unwind: "$order_products"     // Tách mảng kết quả join ra thành các document riêng biệt
  },
  {
    $project: {                    // Chọn các trường cụ thể cần hiển thị
      user_id: 1,
      username: 1,
      "user_orders.order_id": 1,
      "user_orders.order_date": 1,
      "order_products.product_name": 1,
      "order_products.price": 1
    }
  }
])

Cá nhân mình đã làm dự án chat, và đến khi có nghiệp vụ cần lấy phân trang danh sách những người chưa nằm trong danh sách liên hệ mình cảm thấy đuối luôn, nên có lẽ đối với trường hợp cần join bảng phức tạp, mongodb không phải là một lựa chọn hoàn hảo vì nó quá phức tạp tử cả tư tưởng lẫn cú pháp.

Tổng kết

Như vậy chúng ta đã cùng nhau tìm hiểu các vấn đề thực tế khi sử dụng MongoDB và các giải quyết.
Cám ơn bạn đã quan tâm đến bài viết|video này. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a