Nếu bạn đã từng làm việc với cơ sở dữ liệu, bạn sẽ được sự phức tạp trong các câu truy vấn khi dự án ngày một lớn hớn và nhiều khó khăn hơn cho việc debug. Bạn có biết các lập trình viên sẽ đối mặt với vấn đề này như thế nào? Một sự thực là người ta đã không còn dùng SQL raw cho các ứng dụng, trong khoảng thời gian đây. Vậy nếu không SQL thì họ dùng cái gì để truy vấn cơ sở dữ liệu? Câu trả lời chính là ORM/ODM.
Có vô số ORM/ODM đang trôi nổi ngoài kia (sequelize, typeorm, mongoose,…) vì thế ở bài viết này ta không quan tâm đến chúng. Thay vào đó ta sẽ tìm hiểu định nghĩa ODM và ORM, sự khác biệt giữa hai loại, và cách chúng kết nối với cơ sở dữ liệu. Đồng thời so sánh với cách sử dụng SQL raw. Bây giờ ta sẽ bắt đầu trước với ORM.
ORM là gì ?
ORM (Object Relational Mapping) là 1 kỹ thuật lập trình giúp ánh xạ các bảng dữ liệu trong hệ quản trị cơ sở dữ liệu sang dạng đối tượng đang định nghĩa trong các ngôn ngữ lập trình.
Lấy ví dụ, ta có thao tác tạo bảng và thêm vào MySQL như sau:
const mysql = require("mysql");
const conn = mysql.createConnection({
host: "localhost",
user: "your_username",
password: "you_password",
database: "mydb",
});
conn.connect(function (error) {
if (error) {
console.log(error);
} else {
console.log("Connected!");
let createUserTable =
"CREATE TABLE IF NOT EXISTS Users(username VARCHAR(100) NOT NULL, password VARCHAR(200) NOT NULL)";
conn.query(createUserTable, function (error, result) {
if (error) {
console.log(error);
} else {
console.log(result);
}
});
//Inserting User in Users table using SQL query
let sql =
"INSERT INTO Users (username, password) VALUES ('john-doe', 'oomygooturulob')";
conn.query(sql, function (error, result) {
if (error) {
console.log(error);
} else {
console.log(result);
}
});
}
});
Ở đoạn code trên, ta thực hiện thêm dữ liệu (một hàng) vào bảng Users. Ta có thể kiểm tra:
mysql> use mydb
Database changed
mysql> SHOW TABLES;
+----------------+
| Tables_in_mydb |
+----------------+
| Users |
+----------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM Users;
+----------+----------------+
| username | password |
+----------+----------------+
| john-doe | oomygooturulob |
+----------+----------------+
1 row in set (0.00 sec)
Có vẻ mọi thứ thật êm ả, nhưng nếu xui xẻo ta thêm vào một thuộc tính chưa có trong bảng dữ liệu thì sao:
"INSERT INTO Users (username, password, imposter) VALUES ('john-doe', 'oomygooturulob', 'imposter')"
Lúc này ta nhận về thông báo lỗi như sau:
{
code: 'ER_BAD_FIELD_ERROR',
errno: 1054,
sqlMessage: "Unknown column 'imposter' in 'field list'",
sqlState: '42S22',
index: 0,
sql: "INSERT INTO Users (username, password, imposter) VALUES ('john-doe', 'hahalol', 'imposter')"
}
Có thể thấy, khi làm việc với SQL raw ta cần đảm bảo sự đồng nhất giữa code backend và cơ sở dữ liệu. Bất kỳ một sự nhầm lẫn nào cũng có thể gây ra hiệu quả nghiêm trọng. Đặc biệt khi dự án mở rộng, các câu lệnh phức tạp hơn rất nhiều so với INSERT
, lúc đó việc debug sẽ còn khó khăn hơn nữa.
Bây giờ ta sẽ thử với một orm là sequelize như sau:
const Sequelize = require("sequelize");
const sequelize = new Sequelize("mydb1", "your_username", "your_password", {
dialect: "mysql",
});
//Defining User model
const User = sequelize.define("User", {
username: Sequelize.STRING,
password: Sequelize.STRING,
});
sequelize.sync();
//Inserting user
User.create({
username: "john-doe",
password: "okbro",
imposter: "imposter",
}).then(function (user) {
console.log(user);
});
Code ngắn hơn, rõ ràng và trực quan hơn. Đồng thời kết quả vẫn tương tự:
mysql> use mydb1
Database changed
mysql> SHOW TABLES;
+-----------------+
| Tables_in_mydb1 |
+-----------------+
| Users |
+-----------------+
1 row in set (0.01 sec)
mysql> SELECT * FROM Users;
+----+----------+----------+---------------------+---------------------+
| id | username | password | createdAt | updatedAt |
+----+----------+----------+---------------------+---------------------+
| 1 | john-doe | okbro | 2020-11-10 12:25:55 | 2020-11-10 12:25:55 |
+----+----------+----------+---------------------+---------------------+
1 row in set (0.01 sec)
Ở đây khi ta cố chèn thêm thuộc tính imposter
khi thêm dữ liệu. Song ta không nhận bất kỳ thông báo lỗi nào, lý do là vì sequelize chỉ ánh xạ dữ liệu theo model của User. Những thuộc tính không có trong model sẽ bị bỏ qua khi thực thi, do đó không có lỗi.
ODM là gì ?
ODM (Object Document Mapping) là ORM cho các cơ sở dữ liệu NOSQL mà dữ liệu được lưu dưới dạng document như MongoDB, OrientDB, Apache CouchDB,…
Ví dụ khi ta thêm một document vào MongoDB:
const MongoClient = require("mongodb").MongoClient;
const url = "mongodb://localhost:27017/";
MongoClient.connect(url, function (error, db) {
if (error) throw error;
const dbo = db.db("mydb");
const user = { name: "John", password: "helloworld" };
const userImposter = { whatever: "doe", password: "okok" };
//inserting user
dbo.collection("users").insertOne(user, function (error, result) {
if (error) throw error;
console.log(result);
});
//inserting garbage user
dbo.collection("users").insertOne(userImposter, function (error, result) {
if (error) throw error;
console.log(result);
});
});
Bây giờ ta kiểm tra kết quả:
> use mydb
switched to db mydb
> db.users.find().pretty()
{
"_id" : ObjectId("5faa6ee4e66d8823b473d392"),
"name" : "John",
"password" : "helloworld"
}
{
"_id" : ObjectId("5faa6ee4e66d8823b473d393"),
"whatever" : "doe",
"password" : "okok"
}
Ở đây ta có thể trường whatever
trong document thứ hai là dữ liệu rác. Ta cần loại bỏ nó như trường hợp imposter
với ORM.
Bây giờ ta sẽ sử dụng Mongoose như sau:
const mongoose = require("mongoose");
// Database Connection
mongoose.connect("mongodb://127.0.0.1:27017/mydb1", {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true,
});
//user model
const User = mongoose.model("User", {
username: { type: String },
password: { type: String },
});
//new user object
const newUser = new User({
username: "john-doe",
password: "helloworld",
whatever: "hello",
});
//inserting/saving the document in collection
newUser.save(function (error, result) {
if (error) {
console.log(error);
} else {
console.log(result);
}
});
Bây giờ ta sẽ thấy, trường whatever
không xuất hiện trong MongoDB khi thêm dữ liệu:
> use mydb1
switched to db mydb1
> db.users.find().pretty()
{
"_id" : ObjectId("5faa7a2326478a301cf3e9f1"),
"username" : "john-doe",
"password" : "helloworld",
"__v" : 0
}
Như vậy, model trong ODM cũng có chức năng tương tự ORM. Về cơ bản hai thứ này hoạt động trên hai dạng cơ sở dữ liệu khác nhau, ORM dành cho SQL database, còn ODM dành cho document database. Nhưng chức năng của chúng là như nhau.