iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
佛心分享-IT 人自學之術

出發吧!後端菜鳥:30 天的後端學習紀錄系列 第 17 篇

Day 17|讓資料庫操作變得更簡單:ORM

  • 分享至 

  • xImage
  •  

前言

前幾天開始接觸 PostgreSQL,也透過 SQL 學會了如何新增、查詢、修改和刪除資料,到了這裡,已經可以直接透過 SQL 和資料庫溝通,例如使用 SELECT 查詢資料、INSERT 新增資料、UPDATE 修改資料,以及 DELETE 刪除資料。

這種方式非常直接,因為 SQL 本身就是資料庫理解的語言,不過當專案逐漸變大,資料表和資料存取邏輯也會跟著增加,除了 SQL 查詢本身,還需要處理資料庫連線、參數、查詢結果,以及不同資料表之間的關係,這些工作累積之後,資料存取程式碼也會越來越多。

因此在實務開發中,除了直接使用 SQL,也常會透過 ORM 來處理資料庫操作,ORM 並不是取代 SQL,而是在應用程式與資料庫之間增加一層抽象,讓開發者可以用更接近程式資料模型的方式操作資料庫。

ORM 是概念,TypeORM 是工具

ORM 是 Object-Relational Mapping,也就是「物件與關聯式資料庫之間的對應」。

這裡要先區分兩個容易混淆的名稱:ORM 是一種技術概念,而 TypeORM 是實際實作這個概念的工具。

ORM 描述的是一種讓程式中的物件與關聯式資料庫建立對應,並透過程式碼操作資料庫的方式,至於實際使用哪個工具,則可以有不同選擇。

這篇先使用 TypeORM 來實際操作 PostgreSQL,所以可以先把兩者理解成:

ORM
↓
資料庫操作的技術概念

TypeORM
↓
實作 ORM 概念的工具

因此看到 ORM 時,指的是這一整類的技術概念,看到 TypeORM 時,則是指這次實際使用的套件。

ORM 讓程式模型與資料表建立對應

在 JavaScript 裡,我們習慣操作的是物件,例如一筆 Note 可以表示成:

const note = {
  id: 1,
  title: '學習 ORM',
  content: '開始理解 ORM 的用途'
};

但在 PostgreSQL 裡,資料會儲存在資料表中:

notes
--------------------------------
id | title      | content
1  | 學習 ORM   | 開始理解 ORM 的用途

ORM 做的事情,就是建立程式裡的物件與資料庫資料表之間的對應,讓我們可以用程式的資料模型來操作資料。

例如原本直接使用 SQL 查詢所有 notes:

SELECT * FROM notes;

透過 TypeORM 可以寫成:

const notes = await noteRepository.find();

這裡的差別不是 PostgreSQL 突然開始理解 JavaScript,而是 TypeORM 在中間處理了應用程式與資料庫之間的轉換。

程式端操作的是 Repository,底層仍然會產生資料庫可以執行的 SQL,再交給 PostgreSQL 處理。整體流程可以理解成:

Node.js / Express
        ↓
      TypeORM
        ↓
       SQL
        ↓
   PostgreSQL

所以 ORM 的角色,就是讓應用程式不必每一次都直接處理資料庫底層的細節。

Entity 與 Repository

要讓 ORM 知道程式中的資料模型和資料庫怎麼對應,首先需要描述資料結構,在 TypeORM 裡,這個角色就是 Entity。

假設 PostgreSQL 有一張 notes 資料表:

notes
├── id
├── title
├── content
└── created_at

可以在 TypeORM 裡建立對應的 Entity:

const { EntitySchema } = require('typeorm');

const Note = new EntitySchema({
  name: 'Note',
  tableName: 'notes',
  columns: {
    id: {
      type: Number,
      primary: true,
      generated: true
    },
    title: {
      type: String
    },
    content: {
      type: String
    }
  }
});

module.exports = Note;

這裡的 Note 就是 Entity,它描述 notes 這張資料表,以及資料表中的欄位。因此可以建立這樣的對應:

資料庫              程式

users        ←→     User
notes        ←→     Note
comments     ←→     Comment

不同 ORM 或框架可能會使用 Model、Entity 等不同名稱,但目前可以先掌握這個概念:Entity 是用來描述資料庫資料結構,並建立程式模型與資料表之間對應的方式。

有了 Entity 之後,還需要透過 Repository 進行實際的資料庫操作。可以先把兩者理解成:

Note Entity
    ↓
描述資料

Note Repository
    ↓
操作資料

PostgreSQL

也就是 Entity 負責「這筆資料長什麼樣子」,Repository 負責「怎麼對這筆資料進行查詢、新增、修改與刪除」。

ORM 與 Raw SQL

ORM 和 Raw SQL 並不是互相取代,而是不同層次的操作方式。

Raw SQL 直接描述資料庫要執行的查詢,因此對資料庫的控制比較直接。遇到複雜的 JOIN、GROUP BY、Aggregation,或需要使用 PostgreSQL 特有功能時,直接撰寫 SQL 往往會更容易掌握。

ORM 則適合處理大量常見的資料存取,例如 CRUD,這些操作通常結構相似,交給 ORM 後可以用比較接近資料模型的方式處理,也能減少重複的資料庫程式碼。

因此實際開發時,兩者通常會搭配:

一般的 CRUD
→ ORM

複雜的查詢
→ QueryBuilder / Raw SQL

所以使用 ORM 並不代表之後完全不寫 SQL,而是把常見的資料庫操作交給較高階的抽象處理,需要細緻控制時,再回到更接近資料庫的 SQL。

把 Day 16 的 CRUD 改成 TypeORM

到了這裡,直接把 Day 16 學過的 SQL 和 TypeORM 放在一起比較,會最容易理解兩種方式的差異。

假設目前有一張 notes 資料表:

notes
--------------------------------
id
title
content
created_at

查詢全部資料

Day 16 使用 SQL:

SELECT * FROM notes;

使用 TypeORM:

const notes = await noteRepository.find();

這裡 find() 代表取得這個 Repository 對應的資料。

查詢指定資料

SQL:

SELECT *
FROM notes
WHERE id = 1;

TypeORM:

const note = await noteRepository.findOneBy({
  id: 1
});

這段程式是使用條件找到指定的 Note,概念上對應到 SQL 裡的 WHERE id = 1。

新增資料

SQL:

INSERT INTO notes (title, content)
VALUES ('學習 ORM', '開始理解 ORM');

TypeORM:

const note = noteRepository.create({
  title: '學習 ORM',
  content: '開始理解 ORM'
});

const savedNote = await noteRepository.save(note);

這裡的 create() 會建立 Entity,save() 則負責把這個 Entity 儲存到資料庫。save() 不應該單純理解成 INSERT 的另一種寫法,因為它除了新增資料,也可以處理更新既有 Entity 的情況。

修改資料

SQL:

UPDATE notes
SET title = '開始學習 ORM'
WHERE id = 1;

TypeORM:

await noteRepository.update(
  1,
  {
    title: '開始學習 ORM'
  }
);

這裡透過 update() 指定要修改的資料,以及需要更新的欄位。

刪除資料

SQL:

DELETE FROM notes
WHERE id = 1;

TypeORM:

await noteRepository.delete(1);

這裡直接指定要刪除的資料 ID。

從這組對照可以看到,使用 SQL 時,我們會直接描述資料庫要執行的操作;使用 ORM 後,則會透過 Repository 和 Entity 來描述資料模型與資料存取行為。

這並不是把 SELECT、INSERT、UPDATE、DELETE 一一換成 find()、save()、update()、delete() 而已,而是把操作的思考層次從 SQL 往資料模型移動。

ORM 底層仍然需要 SQL

雖然使用 TypeORM 後,程式碼裡不一定會直接出現 SQL,但底層仍然需要透過 SQL 與 PostgreSQL 溝通。

例如:

const notes = await noteRepository.find();

實際上會經過 ORM 產生查詢,再交給 PostgreSQL 執行,最後將結果整理成程式可以使用的資料。

noteRepository.find()
        ↓
      TypeORM
        ↓
       SQL
        ↓
   PostgreSQL
        ↓
   Query Result
        ↓
      TypeORM
        ↓
 JavaScript Object

因此 SQL 仍然很重要。前面先學 SQL,就是為了知道資料庫實際在做什麼;現在開始接觸 ORM,則是進一步理解應用程式可以怎麼更高階地管理這些資料庫操作。

結論

Day 16 學的是如何直接透過 SQL 操作資料庫,Day 17 則是在這個基礎上往上一層,開始理解 ORM 如何把資料庫操作整合進程式架構。

這裡最重要的是分清楚 ORM 和 TypeORM 並不是同一件事情。ORM 是一種讓程式模型與關聯式資料庫建立對應的技術概念,TypeORM 則是這次拿來實作 ORM 的工具。

透過 TypeORM,可以用 Entity 描述資料結構,再透過 Repository 操作資料。對於常見的 CRUD,可以不用每次都自己撰寫完整 SQL,而是用更接近資料模型的方式處理。

不過 ORM 並沒有改變資料庫本身的運作方式,底層仍然是 SQL:

Node.js / Express
        ↓
      TypeORM
        ↓
       SQL
        ↓
   PostgreSQL

因此前面學 SQL 並不是為了之後改用 ORM 就丟掉,而是讓自己知道 ORM 背後到底在做什麼。

到了這裡,除了會操作資料,也開始碰到另一個需要管理的問題:資料庫的結構本身會隨著專案一起變化。

例如未來 notes 新增 status 欄位,就不只是修改 Entity,實際的 PostgreSQL 資料表也需要同步調整,而且還要確保不同環境使用的是相同的資料庫結構。

下一篇,就會進入 Migration,開始管理資料庫結構的變更。


上一篇
Day 16|Express 終於遇見 PostgreSQL
下一篇
Day 18|資料庫結構也要版本管理:認識 Migration
系列文
出發吧!後端菜鳥:30 天的後端學習紀錄 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言