別再手寫 SQL 了!Laravel Eloquent ORM 終極指南:從新手入門到效能優化,一次搞懂 Active Record 的黑魔法

2025/08/15 | Laravel技術分享

別再手寫 SQL 了!Laravel Eloquent ORM 終極指南:從新手入門到效能優化,一次搞懂 Active Record 的黑魔法

哈囉,大家好,我是浪花科技的資深工程師 Eric。寫程式這麼多年,我看過太多專案因為資料庫操作混亂而陷入維護地獄。還記得剛入行的時候,光是拼湊一個複雜的 `JOIN` 查詢,就能耗掉我整個下午,更不用說欄位名稱打錯一個字,那種大海撈針的除錯過程,簡直是工程師的惡夢。

幸好,我們活在一個有 Laravel 的美好時代。而 Laravel 之所以如此優雅,很大一部分要歸功於它的 ORM — Eloquent。今天,我就要帶你深入這個強大的工具,從最基礎的觀念到資深工程師必備的效能優化技巧,讓你徹底告別手寫 SQL 的痛苦,用更直觀、更具表達力的方式來操作資料庫。準備好了嗎?讓我們開始吧!

什麼是 ORM?Eloquent 又扮演什麼角色?

在我們深入 Eloquent 的細節之前,先來聊聊什麼是 ORM。ORM 的全名是 Object-Relational Mapping(物件關聯對應)。這聽起來很學術,但概念其實很簡單:它就像一個翻譯官,幫你在「物件導向的程式語言(例如 PHP)」和「關聯式資料庫(例如 MySQL)」之間建立一座橋樑。

簡單來說,ORM 讓我們可以:

  • 用操作物件的方式來操作資料庫。
  • 將資料庫中的一張資料表(Table)對應到程式中的一個類別(Class)。
  • 將資料表中的一筆紀錄(Row)對應到那個類別的一個實例(Instance/Object)。

而 Eloquent 就是 Laravel 內建的 ORM 實作,它採用了廣受歡迎的 Active Record 設計模式。這意味著每個 Model 類別不僅僅是資料結構的定義,它本身就內建了查詢、新增、修改、刪除等功能。你不需要再另外寫一個 `UserRepository` 來處理 `User` 的資料庫邏輯,`User` Model 自己就能搞定一切。這就是 Active Record 的魔力所在,它讓我們的程式碼更簡潔、更直觀。

Eloquent 基礎:CRUD 操作實戰

講了這麼多理論,是時候來點實際的了。我們假設有一個 `posts` 資料表,用來存放部落格文章。首先,我們要建立一個對應的 Model。

1. 建立 Model

你可以透過 Artisan 指令輕鬆建立一個 Model:

php artisan make:model Post

這會在 `app/Models` 目錄下建立一個 `Post.php` 檔案。一個最基本的 Model 長這樣:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;
}

Eloquent 會自動假設這個 `Post` Model 對應到 `posts` 資料表(類別名稱的蛇形、複數形式)。如果你的資料表名稱不符合這個慣例,也可以手動指定:

protected $table = 'my_posts';

2. 新增資料 (Create)

新增一筆文章資料非常簡單:

<?php
$post = new Post;
$post->title = '我的第一篇文章';
$post->content = '這是一篇關於 Eloquent 的文章...';
$post->is_published = true;
$post->save();

或者,你也可以使用 `create` 方法。不過,為了安全性,使用 `create` 方法前,你必須在 Model 中定義 `$fillable` 屬性,明確告知哪些欄位是允許被大量賦值的,這能有效防止惡意的使用者傳入非預期的欄位資料。

<?php
// 在 Post.php Model 內
protected $fillable = ['title', 'content', 'is_published'];

// 在 Controller 內
Post::create([
    'title' => '使用 Create 方法',
    'content' => '這是一種更簡潔的方式。',
    'is_published' => true,
]);

3. 查詢資料 (Read)

Eloquent 提供了非常豐富的查詢方法:

<?php
// 取得所有文章
$posts = Post::all();

// 根據主鍵 ID 尋找文章
$post = Post::find(1);

// 加上查詢條件
$publishedPosts = Post::where('is_published', true)->get();

// 排序並只取 10 筆
$latestPosts = Post::where('is_published', true)
                   ->orderBy('created_at', 'desc')
                   ->take(10)
                   ->get();

// 取得第一筆符合條件的資料
$firstPost = Post::where('is_published', true)->first();

看看這些程式碼,是不是比手寫 SQL `SELECT * FROM posts WHERE is_published = 1 ORDER BY created_at DESC LIMIT 10` 來得直觀且優雅多了?

4. 更新資料 (Update)

更新資料也很簡單。首先找到你要更新的紀錄,修改屬性後,再呼叫 `save()` 方法。

<?php
$post = Post::find(1);
$post->title = '更新後的標題';
$post->save();

如果你想一次更新多筆符合條件的紀錄,可以使用 `update` 方法:

<?php
Post::where('is_published', false)->update(['is_published' => true]);

5. 刪除資料 (Delete)

刪除資料有兩種方式,一種是找到 Model 實例後刪除,另一種是直接基於查詢條件刪除。

<?php
// 方法一:找到實例後刪除
$post = Post::find(1);
$post->delete();

// 方法二:直接刪除
Post::destroy(1);
Post::destroy([1, 2, 3]); // 刪除多筆

// 方法三:基於查詢條件刪除
Post::where('is_published', false)->delete();

關係的魔力:讓資料庫再次偉大

如果 Eloquent 只能做到 CRUD,那它不過是個語法糖。真正讓它封神的,是處理資料表之間「關係」的能力。你再也不用自己寫 `JOIN` 了!

一對多 (One To Many)

假設一個使用者 (`User`) 可以有多篇文章 (`Post`)。這就是典型的一對多關係。

<?php
// 在 User.php Model 內
public function posts()
{
    return $this->hasMany(Post::class);
}

// 在 Post.php Model 內
public function user()
{
    return $this->belongsTo(User::class);
}

定義好關係後,你可以這樣用:

<?php
// 取得某位使用者的所有文章
$user = User::find(1);
$posts = $user->posts; // 這會回傳一個包含 Post 物件的 Collection

// 取得某篇文章的作者
$post = Post::find(5);
$author = $post->user;

是不是超級直觀?完全不用去想 `foreign key` 和 `primary key` 怎麼關聯。

多對多 (Many To Many)

假設一篇文章 (`Post`) 可以有多個標籤 (`Tag`),一個標籤也可以被用在多篇文章上。這就需要一個中間表(pivot table),例如 `post_tag`。

<?php
// 在 Post.php Model 內
public function tags()
{
    return $this->belongsToMany(Tag::class);
}

// 在 Tag.php Model 內
public function posts()
{
    return $this->belongsToMany(Post::class);
}

使用起來跟一對多一樣簡單,而且 Eloquent 還提供了 `attach()`, `detach()`, `sync()` 這些方便的方法來管理中間表的紀錄。

工程師的必修課:效能優化與 N+1 問題

好了,基礎打完,現在要來談點進階的,這也是我面試新人時最愛問的問題之一。當你開心地使用 Eloquent 關係時,很容易會掉入一個叫做「N+1 查詢問題」的效能陷阱。

什麼是 N+1 問題?

想像一下這個情境:我們要顯示 100 篇文章的列表,並在每篇文章下方顯示作者的姓名。

<?php
$posts = Post::take(100)->get();

foreach ($posts as $post) {
    echo '文章標題: ' . $post->title;
    echo '作者: ' . $post->user->name; // 在這裡觸發了一次資料庫查詢!
}

你看出來問題在哪了嗎?

  • 第 1 次查詢:`SELECT * FROM posts LIMIT 100` (取得 100 篇文章)
  • 接下來的 N (100) 次查詢:在迴圈中,每次存取 `$post->user` 時,Eloquent 都會發起一次新的查詢去 `users` 資料表找作者,例如 `SELECT * FROM users WHERE id = ?`。

總共執行了 1 + 100 = 101 次查詢!這在資料量大時會造成嚴重的效能瓶頸。這就是 N+1 問題。

解決方案:預載入 (Eager Loading)

Eloquent 提供了非常優雅的解決方案:`with()` 方法。它會預先載入所有需要的關聯資料。

<?php
// 使用 with() 來預載入 user 關係
$posts = Post::with('user')->take(100)->get();

foreach ($posts as $post) {
    echo '文章標題: ' . $post->title;
    echo '作者: ' . $post->user->name; // 不會再觸發新的查詢!
}

這樣修改後,Eloquent 只會執行 2 次查詢:

  1. `SELECT * FROM posts LIMIT 100`
  2. `SELECT * FROM users WHERE id IN (1, 2, 3, …)` (一次把所有需要的作者都撈出來)

從 101 次查詢降到 2 次!效能差異是天壤之別。記住:只要你會在迴圈中使用關聯,就一定要用 `with()` 預載入,這是一個鐵則。

結論:Eloquent 不只是工具,更是一種思維

今天我們從 Eloquent 的基本概念、CRUD 操作,一路聊到核心的「關係」以及資深工程師必備的「效能優化」。Eloquent ORM 遠不止這些,它還有 Accessors & Mutators、Query Scopes、Collections 等等強大的功能等著你去探索。

學習 Eloquent,不僅是學習一個工具,更是學習一種用物件導向思維來與資料庫互動的方式。它能讓你的程式碼更乾淨、更具可讀性,也更容易維護。當你不再需要為了拼湊 SQL 而焦頭爛額時,你才能真正專注在更重要的商業邏輯上。

當然,ORM 也不是萬靈丹。在某些極端複雜的查詢或需要極致效能的場景下,回歸 Laravel 的 Query Builder 甚至原生 SQL 也是必要的。但對於 95% 以上的應用場景,Eloquent 絕對是你最可靠的夥伴。

希望這篇完整的 Laravel Eloquent ORM 指南能幫助你打通任督二脈。如果你在開發上遇到了更複雜的架構問題,或是需要導入 Laravel 來改造現有的系統,別忘了浪花科技永遠是你最堅實的後盾。

延伸閱讀

需要專業的 Laravel 技術支援嗎?

在浪花科技,我們專注於提供高品質的 Laravel 與 WordPress 網站開發及企業系統解決方案。無論您是需要從零開始打造一個高效能的後台系統,還是對現有專案的架構與效能感到頭痛,我們的團隊都能提供專業的諮詢與協助。別讓技術問題成為您事業的絆腳石,立即聯繫我們,讓浪花科技的資深工程師團隊為您的專案保駕護航!

常見問題 (FAQ)

Q1: Eloquent ORM 是什麼?為什麼我應該使用它而不是手寫 SQL?

A: Eloquent ORM (Object-Relational Mapping) 是一個讓你可以用 PHP 物件來操作資料庫的工具。它將資料庫的資料表(Table)對應到一個 PHP 類別(Model)。使用它的好處是程式碼更直觀、可讀性更高,且能大幅減少撰寫重複、繁瑣的 SQL 語句。它還內建了許多安全機制,如防止 SQL Injection,並能透過「關係」輕鬆處理複雜的資料表關聯,讓你專注於業務邏輯而非底層的 SQL 語法。

Q2: 什麼是 N+1 查詢問題?如何用 Eloquent 解決?

A: N+1 問題是使用 ORM 時常見的效能陷阱。當你先查詢出一組主資料(1 次查詢),然後在迴圈中逐一去讀取每筆主資料的關聯資料時,會為每一筆主資料都發起一次新的查詢(N 次查詢),總共導致 N+1 次查詢。在 Eloquent 中,解決方案是使用「預載入」(Eager Loading),也就是在第一次查詢時就用 `with(‘relationName’)` 方法,告訴 Eloquent 預先把所有需要的關聯資料一次性撈取出來,將 N+1 次查詢大幅減少為 2 次查詢,顯著提升效能。

Q3: Eloquent 和直接使用 Query Builder 有什麼不同?我該如何選擇?

A: Eloquent 是基於 Query Builder 的高階抽象層,它提供了 Active Record 模式的實作,讓你可以操作 Model 物件,並且內建了「關係」、「事件」等強大功能。Query Builder 則是一個更底層的資料庫查詢建構器,語法更接近原生 SQL。選擇的原則是:對於大部分的 CRUD 和有關聯性的操作,優先使用 Eloquent,因為它程式碼更優雅、開發速度更快。當遇到非常複雜、需要極致效能優化的報表查詢,或 Eloquent 無法輕易表達的查詢時,可以降級使用 Query Builder 來獲得更大的靈活性。

Q4: 我可以在 Eloquent 中執行原生 SQL 查詢嗎?

A: 可以的。雖然 Eloquent 鼓勵使用物件導向的方式操作資料庫,但在某些特殊情況下,Laravel 仍然允許你執行原生 SQL 查詢。你可以使用 `DB` facade 提供的方法,例如 `DB::select(‘SELECT * FROM users’)` 或 `DB::statement(‘UPDATE …’)`。這提供了最大的靈活性,但請務必注意手動處理 SQL 注入的風險,例如使用參數綁定來傳遞變數。

 
立即諮詢,索取免費1年網站保固