Bạn có thể sử dụng javascript làm cơ sở dữ liệu không?

Nếu bạn đang phát triển ứng dụng web, gần như chắc chắn bạn sẽ liên tục tương tác với cơ sở dữ liệu. Và khi đến lúc phải chọn cách bạn sẽ tương tác, các lựa chọn có thể khiến bạn choáng ngợp

Trong bài viết này, chúng ta sẽ xem xét chi tiết 5 cách khác nhau để tương tác với cơ sở dữ liệu của bạn bằng JavaScript và chúng ta sẽ nói về ưu và nhược điểm của từng cách. Chúng ta sẽ bắt đầu với lựa chọn cấp thấp nhất — Lệnh SQL — sau đó chuyển sang phần tóm tắt cấp cao hơn

Việc chọn thư viện cơ sở dữ liệu phù hợp cho ứng dụng JavaScript của bạn có thể có tác động lớn đến khả năng bảo trì, khả năng mở rộng và hiệu suất của mã của bạn, vì vậy bạn nên dành thời gian để tìm ra các tùy chọn của mình

Ứng dụng mẫu của chúng tôi

Chúng tôi sẽ sử dụng một ứng dụng Express tầm thường được lưu trữ trên Heroku làm ví dụ của chúng tôi. Tất cả mã cho bài viết này đều có trong kho GitHub này. Hãy sao chép nó và làm theo

điều kiện tiên quyết

Để chạy ứng dụng mẫu, bạn sẽ cần có phần mềm sau trên máy của mình

  • Một môi trường đầu cuối giống như unix (Mac OSX và Linux đều ổn. Nếu bạn đang sử dụng Windows, bạn sẽ cần Hệ thống con Windows cho Linux)
  • git (và tài khoản github)
  • npm (phiên bản 6 trở lên)
  • Công cụ dòng lệnh Heroku

Nếu bạn chưa có tài khoản Heroku, bạn sẽ cần đăng ký một tài khoản miễn phí. Nếu bạn không muốn đăng ký Heroku, bạn cũng có thể chạy ứng dụng cục bộ dựa trên phiên bản Postgres cục bộ. Nếu bạn cảm thấy thoải mái với điều đó, sẽ khá dễ dàng để biết bạn cần thực hiện những thay đổi nào thay vì triển khai lên Heroku

Khi bạn đã cài đặt tất cả những thứ trên, hãy chạy

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
3 trong một thiết bị đầu cuối và bạn đã sẵn sàng để bắt đầu

Xây dựng và triển khai ứng dụng Hello World

Để bắt đầu, chúng tôi sẽ thiết lập như sau

  • Một ứng dụng Express tầm thường chỉ phục vụ trang web “Xin chào, Thế giới”
  • Cơ sở dữ liệu Postgres
  • Hai bảng, đại diện cho “người dùng” và “bình luận” (một người dùng có nhiều bình luận)
  • Một số dữ liệu mẫu (trong trường hợp này, được tạo qua mockaroo. com)

Tôi đã tạo một ứng dụng mẫu sẽ thiết lập tất cả những thứ này cho bạn (miễn là bạn đã chạy

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
3 như đã đề cập ở trên). Để thiết lập nó, vui lòng thực hiện các lệnh sau từ dòng lệnh

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
5

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
6

Quá trình này sẽ mất vài phút để hoàn thành. Trong khi chờ đợi, bạn có thể xem tệp thực hiện để xem các lệnh có liên quan, thực hiện như sau

  • Tạo một ứng dụng Heroku mới
  • Thêm một phiên bản cơ sở dữ liệu Postgres
  • Triển khai ứng dụng lên Heroku
  • Chạy lệnh trên Heroku để thiết lập bảng cơ sở dữ liệu và nhập dữ liệu mẫu CSV
  • Mở URL của ứng dụng Heroku của bạn trong cửa sổ trình duyệt mới

Khi kết thúc quá trình này, bạn sẽ thấy “Xin chào, Thế giới” trên một trang web

Tìm nạp dữ liệu bằng SQL

OK — chúng ta đã thiết lập xong. Chúng tôi đã tạo một cơ sở dữ liệu với hai bảng và một số dữ liệu mẫu. Nhưng chúng tôi vẫn chưa làm gì với nó. Bước tiếp theo là cho phép ứng dụng web của chúng tôi truy xuất dữ liệu từ cơ sở dữ liệu

Bất cứ khi nào bạn tương tác với cơ sở dữ liệu quan hệ, bạn làm như vậy bằng cách gửi các lệnh SQL tới ổ cắm mạng mà cơ sở dữ liệu đang lắng nghe. Điều này đúng với tất cả các thư viện mà chúng ta sẽ xem xét trong bài viết này — ở mức thấp nhất, tất cả chúng đều gửi các lệnh SQL tới cơ sở dữ liệu và truy xuất bất kỳ kết quả nào được trả về

Vì vậy, cách đầu tiên mà chúng ta sẽ xem xét khi tương tác với cơ sở dữ liệu của mình là thực hiện điều đó — gửi các lệnh SQL. Để làm điều này, chúng tôi sẽ cài đặt thư viện JavaScript pg, cho phép chúng tôi gửi SQL tới cơ sở dữ liệu Postgres và truy xuất kết quả

Để cài đặt thư viện pg, hãy thực hiện lệnh sau

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
7

Điều này sẽ tìm nạp và cài đặt thư viện và nó sẽ thêm nó vào gói của bạn. json và khóa gói. tập tin json. Hãy cam kết những thay đổi đó

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
8

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
9

Để nói chuyện với cơ sở dữ liệu của chúng tôi, chúng tôi cần một số chi tiết

  • Tên máy chủ của máy Postgres đang chạy trên
  • Cổng mạng Postgres đang lắng nghe
  • Tên của cơ sở dữ liệu dữ liệu của chúng tôi là trong
  • Tên người dùng và mật khẩu có quyền truy cập dữ liệu đó

Hầu hết các thư viện cơ sở dữ liệu sẽ cho phép chúng tôi thiết lập kết nối bằng cách cung cấp một đối tượng cho thư viện có khóa và giá trị cho tất cả các chi tiết đó hoặc bằng cách kết hợp tất cả chúng vào một "URL cơ sở dữ liệu" duy nhất, đó là những gì chúng tôi sẽ làm

Khi bạn thêm cơ sở dữ liệu vào ứng dụng Heroku, bạn sẽ tự động nhận được một biến môi trường có tên DATABASE_URL, chứa tất cả thông tin chi tiết bạn cần để kết nối với cơ sở dữ liệu. Bạn có thể thấy giá trị của DATABASE_URL bằng cách chạy

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
0

Điều này sẽ xuất ra tất cả các biến môi trường mà ứng dụng của bạn có thể sử dụng. Hiện tại chỉ nên có một cái, vì vậy bạn sẽ thấy một cái gì đó như thế này ở đầu ra

DATABASE_URL.

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
1

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
2

Trong trường hợp ví dụ của chúng tôi, nó bị hỏng như thế này

SQL

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
0

Giá trị DATABASE_URL của bạn sẽ khác, nhưng cấu trúc sẽ giống nhau

Bây giờ chúng ta đã cài đặt thư viện pg và chúng ta biết cách kết nối với cơ sở dữ liệu của mình, hãy thực hiện ví dụ đầu tiên về tương tác với cơ sở dữ liệu. Chúng tôi chỉ cần lấy danh sách người dùng và hiển thị chúng trên trang web của chúng tôi. Ở đầu chỉ mục của chúng tôi. js, chúng tôi sẽ yêu cầu thư viện pg của mình và tạo một đối tượng kết nối cơ sở dữ liệu

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
1

Trong khối

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
3, chúng ta sẽ thay đổi dòng get để gọi phương thức hiển thị danh sách người dùng từ cơ sở dữ liệu

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
4

Cuối cùng, chúng tôi sẽ triển khai chức năng listUsers

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}

Mã này đợi cho đến khi kết nối được thiết lập với cơ sở dữ liệu của chúng tôi, sau đó gửi truy vấn SQL bằng chức năng truy vấn và truy xuất kết quả

Bây giờ, bước này có thể không thành công vì nhiều lý do khác nhau, vì vậy, trong mã, chúng tôi kiểm tra để đảm bảo rằng chúng tôi có một số dữ liệu và nếu có, chúng tôi sẽ chỉ định kết quả. hàng cho người dùng chính của đối tượng kết quả của chúng tôi. Tiếp theo, chúng tôi chuyển kết quả cho chức năng kết xuất, sau đó giải phóng kết nối cơ sở dữ liệu của chúng tôi

Trong lượt xem/trang/chỉ mục. ejs, chúng tôi có quyền truy cập vào đối tượng kết quả, vì vậy chúng tôi có thể hiển thị dữ liệu người dùng của mình như thế này

HTML

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>

Bạn có thể xem mã với những thay đổi này tại đây.

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
5 và

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
6 là tên của hai cột từ bảng người dùng trong cơ sở dữ liệu của chúng tôi

Hãy triển khai những thay đổi này để chúng ta có thể xem dữ liệu trong ứng dụng Heroku của mình

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
7

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
8

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
9

Quá trình này sẽ mất một hoặc hai phút để triển khai. Khi lệnh đó thực thi xong, hãy tải lại trình duyệt của bạn và bạn sẽ thấy danh sách người dùng trên trang web

Ví dụ MySQL

Ví dụ trên là dành cho Postgres, nhưng mã cho các cơ sở dữ liệu quan hệ phổ biến khác sẽ tương tự. Ví dụ: nếu bạn đang sử dụng MySQL

  • Thay vì sử dụng
    async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
    7, hãy sử dụng
    async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
    21 (sử dụng mysql2, không phải mysql - mysql2 nhanh hơn và hỗ trợ async/await)
  • trong chỉ mục. js, bạn sẽ yêu cầu mysql như thế này

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
22

  • Hàm listUsers sẽ trông như thế này

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
2

lượt xem/trang/chỉ mục. ejs vẫn giữ nguyên

Bạn có thể xem dự án mẫu với những thay đổi này tại đây

Bây giờ, hãy xem xét một vài thư viện được xây dựng trên nền tảng này, thêm các lớp trừu tượng cho phép bạn đọc và thao tác dữ liệu cơ sở dữ liệu theo cách “giống JavaScript” hơn

Cho đến giờ, chúng ta đã biết cách gửi SQL thô tới cơ sở dữ liệu;

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
23

Nếu chúng tôi muốn nhận được nhận xét của một người dùng cụ thể, giả sử người dùng có id là 1, chúng tôi có thể sử dụng một cái gì đó như thế này

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
24

Không có gì sai khi tương tác với cơ sở dữ liệu của bạn theo cách này, nhưng nó có thể hơi cồng kềnh và nó yêu cầu bạn phải luôn “sang số” trong tâm trí. Bạn nghĩ về mã JavaScript của mình theo một cách, nhưng khi bạn cần nghĩ về dữ liệu trong cơ sở dữ liệu của mình, bạn phải bắt đầu suy nghĩ bằng SQL

Mục đích của phần còn lại của các thư viện cơ sở dữ liệu mà chúng ta sẽ xem xét là để cho phép bạn xử lý dữ liệu trong cơ sở dữ liệu của mình giống với các đối tượng JavaScript và mã trong ứng dụng của bạn hơn. “Dưới mui xe” đó là tất cả SQL, nhưng bạn sẽ không cần quan tâm nhiều đến điều đó trừ khi bạn muốn

Knex — Trừu tượng hóa SQL

Thư viện đầu tiên chúng ta sẽ nói đến là Knex. Trang tài liệu mô tả Knex là một “trình tạo truy vấn” và mục đích của nó là cung cấp một lớp trừu tượng trên SQL thô

Cài đặt Knex

Knex yêu cầu pg (hoặc MySQL nếu bạn đang sử dụng cơ sở dữ liệu MySQL). Chúng tôi đã cài đặt pg, vì vậy chúng tôi chỉ cần thêm knex như thế này

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
25

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
26

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
27

Sử dụng Knex

Trang NPM cho knex mô tả nó như một “trình tạo truy vấn. ” Knex trừu tượng hóa SQL ở một mức độ nhất định, nhưng không xa lắm. Chúng ta vẫn cần hiểu SQL cơ bản, nhưng chúng ta có thể viết nó theo cú pháp giống JavaScript hơn, thay vì phải cắt và xâu chuỗi SQL. Quan trọng hơn, chúng ta có thể sử dụng thành phần để xâu chuỗi các thuật ngữ knex theo cách thuận tiện hơn nhiều cho các lập trình viên JavaScript

Vì vậy, khi chúng tôi sử dụng pg, chúng tôi đã có tuyên bố này

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
28

Khi chúng ta sử dụng knex, chúng ta có thể viết cái này

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
29

Điều đó có vẻ không có nhiều khác biệt, nhưng do cách chúng ta có thể soạn lệnh gọi hàm knex, chúng ta cũng có thể làm những việc như thế này

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
20

Ở đây, chúng tôi nhận được 5 bản ghi người dùng, bắt đầu từ vị trí thứ 8 trong tổng số tất cả các bản ghi người dùng có thể phù hợp với truy vấn của chúng tôi. Bạn có thể xem đầy đủ các tùy chọn có sẵn trong tài liệu knex

Hãy thay đổi ứng dụng Express của chúng tôi để sử dụng knex để hiển thị một số bản ghi từ cơ sở dữ liệu của chúng tôi. Đầu tiên, trong chỉ mục. js thay thế hai dòng này

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
2

…Với cái này

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
4

Sau đó, thay đổi cách triển khai của

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
21 thành điều này

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
6

Lượt xem/trang/chỉ mục của chúng tôi. ejs có thể giữ nguyên như trước

Cam kết, đẩy và triển khai

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
22

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
23

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
9

Khi bạn làm mới trình duyệt của mình, bạn sẽ thấy các bản ghi người dùng từ 6 đến 10 trên trang

Bạn có thể xem mã với những thay đổi này tại đây

Ánh xạ quan hệ đối tượng (ORM)

Knex cung cấp cho chúng tôi cách tương tác với cơ sở dữ liệu của chúng tôi, cách này giống JavaScript hơn nhiều, nhưng chúng tôi vẫn phải suy nghĩ theo cách tập trung vào cơ sở dữ liệu khi cần thao tác dữ liệu

Ba thư viện tiếp theo mà chúng ta sẽ nói đến đều được xây dựng trên knex (được xây dựng trên pg hoặc MySQL) và là các ví dụ về “ánh xạ quan hệ đối tượng” hoặc thư viện ORM. Như tên ngụ ý, mục đích của thư viện ORM là dịch giữa dữ liệu trong cơ sở dữ liệu quan hệ và các đối tượng JavaScript trong ứng dụng của bạn. Điều đó có nghĩa là, thay vì nghĩ về các bản ghi trong bảng người dùng khi bạn viết mã JavaScript, bạn có thể nghĩ về các đối tượng người dùng

Sự phản đối

Thư viện đầu tiên chúng ta sẽ xem xét là sự phản đối, được xây dựng trên knex

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
25

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
26

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
27

Để làm nổi bật một số tiện ích của thư viện ORM, chúng tôi sẽ thay đổi ứng dụng của mình để hiển thị người dùng và nhận xét của họ. Sự phản đối được xây dựng dựa trên knex, vì vậy trong chỉ mục của chúng tôi. js, chúng ta phải để nguyên khối knex và thêm một chút mã (tôi đang đặt mọi thứ vào chỉ mục. js, để giữ cho mọi thứ đơn giản. Trong một ứng dụng thực tế, bạn sẽ chia mã thành các tệp riêng biệt)

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
28

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
29

Điều này cho chúng ta một lớp Model mà từ đó chúng ta có thể kế thừa để định nghĩa hai lớp Người dùng và Nhận xét. Chúng tôi sẽ xác định Bình luận đầu tiên

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
5

Lớp của chúng tôi cần mở rộng

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
40 và phải triển khai hàm
async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
41 để báo cho Phản đối bảng cơ sở dữ liệu nào chứa các bản ghi cơ bản

Lớp

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
42 cũng tương tự, nhưng chúng ta sẽ thêm một số hành vi vào lớp của mình; . Chúng tôi cũng sẽ nói với Phản đối rằng
async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
44 có
async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
45 (tôi. e. người dùng sở hữu không hoặc nhiều nhận xét). Trong ORM-speak, điều này thường được mô tả là "có nhiều mối quan hệ" - tôi. e. một người dùng có nhiều bình luận. Đây là mã cho cái này trông như thế nào

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
2

Chúng tôi xác định một đối tượng

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
46 bên trong lớp
async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
42 của chúng tôi, với một khóa nhận xét và một giá trị cho Phản đối rằng đây là một
async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
48 trên lớp
async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
49, trong đó giá trị của cột id của bảng người dùng khớp với giá trị của cột user_id của

Bây giờ chúng ta đã định nghĩa các lớp của mình, hãy sử dụng chúng trong mã của chúng ta. Đây là triển khai mới của

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
21

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
10

Ở đây, chúng tôi tìm nạp 5 người dùng, sau đó với mỗi người dùng đó, chúng tôi tìm nạp nhận xét của họ và gán những nhận xét đó cho thuộc tính nhận xét của đối tượng người dùng của chúng tôi. Trong lượt xem/trang/chỉ mục. ejs, chúng tôi có thể hiển thị người dùng và nhận xét của họ như thế này

HTML

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
11

Bạn có thể xem mã với những thay đổi này tại đây. Như thường lệ, cam kết và đẩy để triển khai

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
7

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
62

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
9

Bây giờ, khi bạn tải lại trang, bạn sẽ thấy người dùng và nhận xét

Vấn đề “N+1 lựa chọn”

Đoạn mã này nhấn mạnh một vấn đề phổ biến mà mọi người gặp phải khi sử dụng thư viện ORM, được gọi là vấn đề “N+1 lựa chọn”

Đây là khối mã chúng tôi đã sử dụng để tìm nạp người dùng và nhận xét của họ

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
12

Điều này hoạt động, nhưng rất không hiệu quả. Đầu tiên, chúng tôi tìm nạp 5 người dùng, sau đó với mỗi người trong số 5 người dùng đó, chúng tôi tìm nạp nhận xét của họ bằng cách thực hiện một cuộc gọi khác tới cơ sở dữ liệu. Vì vậy, chúng tôi đã thực hiện 1 cuộc gọi cho người dùng, sau đó là 5 cuộc gọi khác để lấy ý kiến. Đó là 5 cuộc gọi cộng với 1 cuộc gọi đầu tiên, tôi. e. 5+1 hoặc N+1 trong đó N == 5. Do đó vấn đề “N+1 lựa chọn”

Trừ khi các truy vấn cơ sở dữ liệu của bạn rất phức tạp, thời gian cần thiết để thực hiện cuộc gọi khứ hồi tới cơ sở dữ liệu sẽ lâu hơn rất nhiều so với thời gian cơ sở dữ liệu tính toán và truyền kết quả truy vấn của bạn. Vì vậy, để giữ cho các ứng dụng của chúng ta chạy nhanh, chúng ta cần giảm thiểu số lần gọi đến cơ sở dữ liệu nhiều nhất có thể. Đoạn mã trên hoàn toàn ngược lại với điều này

Đối với ví dụ nhỏ này, bạn sẽ không nhận thấy bất kỳ sự khác biệt nào, nhưng đối với các ứng dụng trong thế giới thực, hiệu suất có thể rất nghiêm trọng và gây ra nhiều vấn đề

May mắn thay, mọi thư viện ORM đều có các tính năng giúp bạn dễ dàng tránh được sự cố này (miễn là bạn biết nó ở đó). Đây là cách Phản đối thực hiện nó; . js, hãy thay thế khối mã ở trên bằng khối này

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
64

Dòng này hoạt động tương tự như khối mã ở trên, nhưng theo cách hiệu quả hơn nhiều về cơ sở dữ liệu. Phản đối sẽ sử dụng thông tin về mối quan hệ mà chúng tôi đã cung cấp để tìm ra cách tìm nạp dữ liệu người dùng và dữ liệu nhận xét trong một truy vấn duy nhất, đồng thời giải nén và kết hợp các kết quả vào cùng một cấu trúc đối tượng mà chúng tôi đã tạo trước khi sử dụng vòng lặp for của mình

Bạn có thể xem mã với những thay đổi này tại đây

Giá sách

Thư viện ORM tiếp theo mà chúng ta sẽ xem xét là Bookshelf

Rất nhiều sự khác biệt giữa các thư viện ORM phụ thuộc vào trường hợp sử dụng mà thư viện được tối ưu hóa cho. Trong trường hợp của Bookshelf, rõ ràng nó được thiết kế để giúp hiển thị danh sách dữ liệu được phân trang dễ dàng nhất có thể, đây là trường hợp sử dụng rất phổ biến trong các ứng dụng web

Hãy thay thế Objection bằng Bookshelf trong ứng dụng của chúng ta

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
65

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
66

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
67

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
68

trong chỉ mục. js, thay thế những dòng này

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
13

…Với cái này

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
14

Thay thế các định nghĩa lớp của chúng tôi bằng các định nghĩa này

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
15

Chức năng

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
21 của chúng tôi bây giờ trông như thế này

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
16

Như bạn có thể thấy, định nghĩa của các lớp ngắn gọn hơn một chút, nhưng Bookshelf cần một định nghĩa chi tiết hơn về cách giải nén dữ liệu của chúng tôi để xây dựng cấu trúc người dùng/nhận xét. Cũng lưu ý cách khái niệm về các trang dữ liệu được tích hợp trực tiếp vào API của thư viện

Mã trong lượt xem/trang/chỉ mục. ejs gần như giống hệt nhau (Tôi đã xóa hàm fullName khỏi lớp Người dùng)

HTML

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
17

Bạn có thể xem mã với những thay đổi này tại đây. Và tất nhiên, một lần nữa cam kết và triển khai

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
7

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
51

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
9

phần tiếp theo

Thư viện cuối cùng chúng ta sẽ xem xét là Sequelize

Phần tiếp theo khá kiên định theo cách nó mong muốn dữ liệu của bạn được cấu trúc. Nếu bạn tuân theo các quy ước của nó, bạn có thể viết ít mã hơn và cho phép Sequelize thực hiện nhiều công việc cho bạn. Đặc biệt, Sequelize có rất nhiều tính năng hỗ trợ tạo bảng cho bạn, mặc định nó sẽ tự tạo theo cấu trúc và quy ước đặt tên riêng.

Cơ sở dữ liệu chúng tôi đang sử dụng không được cấu trúc chính xác theo cách Sequelize mong đợi, vì vậy chúng tôi cần thêm một chút cấu hình bổ sung để cho phép Sequelize hoạt động với nó

Cài đặt phần tiếp theo

Để xóa giá sách và cài đặt phần tiếp theo, hãy chạy các lệnh sau

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
53

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
54

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
26

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
56

Sử dụng phần tiếp theo

trong chỉ mục. js, thay thế những dòng này

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
18

…với những

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
19

Sau đó, thay thế các định nghĩa lớp cho Người dùng và Nhận xét bằng mã này

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
0

Lưu ý rằng chúng tôi đã chuyển hai đối tượng tới

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
57. Đối tượng đầu tiên xác định các thuộc tính của đối tượng của chúng tôi và đối tượng thứ hai chứa một số siêu dữ liệu

Trong trường hợp này, chúng tôi đã nói với Sequelize rằng bảng cơ sở dữ liệu làm cơ sở cho lớp Người dùng được gọi là 'người dùng' (theo mặc định, Sequelize sẽ suy ra rằng bảng đó được gọi là 'Người dùng') và

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
58 nói với Sequelize rằng bảng của chúng tôi không có dấu thời gian

Sequelize giúp bạn dễ dàng viết mã để tạo bảng cho bạn và khi làm như vậy, nó sẽ thêm các cột dấu thời gian này và đặt giá trị của chúng tương ứng khi bạn ghi vào cơ sở dữ liệu. Tài liệu về phần tiếp theo rất xuất sắc và có thêm thông tin về điều này

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
59 mà chúng tôi chuyển đến hasMany là một trong những nơi khác mà chúng tôi phải nói với Sequelize rằng chúng tôi không tuân theo các quy ước của nó. Nó mong đợi (và sẽ tạo cho chúng tôi) một cột có tên UserId để liên kết nhận xét với người dùng

Bên trong chức năng

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
21 của chúng tôi, chúng tôi có thể thay thế tất cả mã này

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
1

…với dòng duy nhất này

JavaScript

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
2

Chúng tôi cũng phải thực hiện một thay đổi nhỏ trong lượt xem/trang/chỉ mục. ejs. Thay thế dòng này

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
21

…với điều này (sự khác biệt là người dùng. Nhận xét thay vì người dùng. bình luận)

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
22

Bạn có thể xem mã với những thay đổi này tại đây

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
7

async function listUsers(req, res) {try {const db = await conn.connect()const result = await db.query('SELECT * FROM users');const results = { users: (result) ? result.rows : null};res.render('pages/index', results );db.release();} catch (err) {console.error(err);res.send("Error " + err);}}
24

Users

    <% users.map((user) => { %>
  • <%= user.id %> - <%= user.first_name %> <%= user.last_name %>
  • <% }); %>
9

Vậy lựa chọn nào là tốt nhất?

Vậy là bạn đã có nó — 5 cách bạn có thể truy vấn cơ sở dữ liệu quan hệ từ ứng dụng JavaScript của mình. Chúng tôi bắt đầu với SQL thô thông qua thư viện pg/mysql, sau đó xem trình tạo truy vấn knex, trước khi chuyển sang ba thư viện ORM;

Vì vậy, đó là sự lựa chọn đúng cho ứng dụng của bạn?

Như mọi khi, nó phụ thuộc vào. Không có gì bạn có thể làm với thư viện ORM mà bạn không thể làm bằng trình tạo truy vấn hoặc thậm chí SQL thô. Vì mọi thứ đều hoạt động bằng cách sử dụng SQL “dưới mui xe”. Điều đó không có gì đáng ngạc nhiên. Ngoài ra, ngay cả khi bạn quyết định sử dụng ORM, hầu hết các thư viện vẫn cung cấp cho bạn cách gửi SQL thô tới cơ sở dữ liệu của bạn. Vì vậy, mức độ trừu tượng mà bạn sử dụng tùy thuộc vào vấn đề bạn đang cố gắng giải quyết và loại mã bạn muốn tập trung vào

Nếu bạn đang sử dụng nhiều các tính năng của cơ sở dữ liệu của mình, có thể với các dạng xem phức tạp hoặc các thủ tục được lưu trữ, thì bạn có thể thấy việc sử dụng knex hoặc SQL thô sẽ dễ dàng hơn. Tuy nhiên, đối với hầu hết các ứng dụng web, rất có thể thư viện ORM sẽ giúp cuộc sống của bạn dễ dàng hơn bằng cách trừu tượng hóa cấu trúc bảng và cho phép bạn xem dữ liệu ứng dụng của mình dưới dạng các đối tượng JavaScript

Nếu bạn đã quyết định chọn ORM, việc lựa chọn sử dụng thư viện ORM nào không phải lúc nào cũng rõ ràng. Khung cảnh của các thư viện JavaScript rất năng động. Các thư viện mới được tạo khá thường xuyên và những thư viện cũ không được ưa chuộng. Dưới đây là một vài điều cần suy nghĩ khi đưa ra lựa chọn của bạn