SQL vs SQL Builder vs ORM

與關聯式資料庫對話:SQL vs SQL Builder vs ORM

前言

開發網站無可避免的會和關聯式資料庫打交道,而互動的方式有三種:

  • SQL
  • SQL Builder
  • Object-Relational Mapping (ORM)

這三者有著不同的使用方式、優缺點與適用場景,了解它們的差異能幫助我們做出更適合專案需求的技術選擇。

SQL

SQL 是與關聯式資料庫溝通的語言,雖然不同資料庫常見語法上有些許差異,但彼此間差異不大。想要獲得最好的彈性與效能可以使用 SQL,極大的彈性也可能暗藏 SQL Injection 風險。

// ❌ 用戶可能添加惡意輸入造成拼湊出惡意的 SQL 指令
const username = req.body.username;
const password = req.body.password;
const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
db.query(query, (err, results) => {
if (results.length > 0) {
// 登入成功
} else {
// 登入失敗
}
});
// ✅ 透過 Prepared Statement 安全處理用戶輸入防止 SQL injection
const username = req.body.username;
const password = req.body.password;
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(query, [username, password], (err, results) => {
// 安全地處理查詢
});

SQL Builder

介於靈活性與安全性之間的方案,透過抽象函式組合,以程式碼的方式構建 SQL,進而迴避 SQL Injection 問題。

// Drizzle ORM
import { eq, and } from 'drizzle-orm';
import { db } from './db';
import { users } from './schema';
const username = req.body.username;
const password = req.body.password;
const result = await db
.select()
.from(users)
.where(and(
eq(users.username, username),
eq(users.password, password)
));
if (result.length > 0) {
// 登入成功
} else {
// 登入失敗
}

ORM

提供一種將資料表對映成程式中的 Class、資料列對應成物件 Object 的方式。通常 ORM 也會處理不同 DB 之間的差異、SQL Injection 問題。

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const username = req.body.username;
const password = req.body.password;
const user = await prisma.user.findFirst({
where: {
username,
password,
},
});
if (user) {
// 登入成功
} else {
// 登入失敗
}

我的學習經驗

在 Node.js 生態系中我使用 MySQL2🔗 與本地資料庫溝通,但 SQL 返回的資料沒有 TypeScript 型別是一大問題,這個問題通常透過 ORM 框架透過定義好的 Schema 來生成 TypeScript 型別。

發現 ORM 之間的經歷轉移麻煩且上手難度高,既要理解 ORM 的抽象,又要在必要時能理解其背後的 SQL 會是很頭疼的問題,因此我會選擇盡可能少量抽象的 SQL Builder 來熟悉 SQL 語法,同時享受它帶來的便利,在 TS 生態系裡面推薦 Drizzle ORM🔗 有很完善的 SQL Builder ~ ORM 體驗。

總結

特性SQLSQL BuilderORM
操作彈性
學習門檻
安全性低(自行防範)
效能掌控最高
適合場景著重效能通用增刪查改為主

延伸閱讀