跳转到主要内容

什么是关联

数据库中的表之间经常存在关联。例如一篇博客文章有多条评论,一笔订单归属于下单的用户。 Eloquent 提供了简洁定义和操作这些关联的方式。关联作为 Eloquent 模型的方法进行定义。
本页以 UserPostCommentTag 等模型为例。假设这些模型与表已经创建好了。
Eloquent 支持的主要关联类型:
关联说明
hasOne一对一(父拥有一个子)
belongsTo一对一的反向(子属于父)
hasMany一对多(父拥有多个子)
belongsToMany多对多(通过中间表)

hasOne(一对一)

hasOne 表示某模型拥有另一个模型的一条记录。例如 User 拥有一个 Profile

定义关联

User 模型定义 profile 方法:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;

class User extends Model
{
    public function profile(): HasOne
    {
        return $this->hasOne(Profile::class);
    }
}
Eloquent 根据父模型名推断外键,此处会假定 profiles 表有 user_id 列。

获取关联数据

关联可以作为属性访问:
$user = User::find(1);
$profile = $user->profile;
以属性方式调用关联方法时,Eloquent 会自动发起查询并返回数据。这称为「动态关联属性」。

belongsTo(一对一的反向)

belongsTohasOne 的反向,定义在拥有外键的一侧。例如 Profile 属于 User

定义关联

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Profile extends Model
{
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}
Eloquent 会用「方法名 + _id」(此处为 user_id)作为外键。

获取关联数据

$profile = Profile::find(1);
$user = $profile->user;

echo $user->name;

hasMany(一对多)

hasMany 是最常见的一对多关联。例如一篇 Post 拥有多条 Comment

定义关联

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Post extends Model
{
    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}
Eloquent 会假定 comments 表有 post_id

获取关联数据

hasMany 返回集合:
$post = Post::find(1);

foreach ($post->comments as $comment) {
    echo $comment->body;
}
也可以追加查询条件:
$recentComments = Post::find(1)
    ->comments()
    ->latest()
    ->take(5)
    ->get();

反向关联(belongsTo)

从评论访问文章:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Comment extends Model
{
    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}
$comment = Comment::find(1);
echo $comment->post->title;

belongsToMany(多对多)

多对多用于两个模型互相拥有多个关联。例如 Post 有多个 TagTag 也属于多个 Post

表结构

多对多需要中间表。例如 PostTag 使用 post_tag 中间表:
posts
    id - integer
    title - string

tags
    id - integer
    name - string

post_tag
    post_id - integer
    tag_id - integer
Eloquent 会按字母顺序拼接两个模型名推断中间表名(post + tagpost_tag)。

定义关联

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Post extends Model
{
    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class);
    }
}
Tag 上同样定义即可反向访问:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Tag extends Model
{
    public function posts(): BelongsToMany
    {
        return $this->belongsToMany(Post::class);
    }
}

获取关联数据

$post = Post::find(1);

foreach ($post->tags as $tag) {
    echo $tag->name;
}

增删关联数据

attach 添加、detach 删除中间表记录:
$post = Post::find(1);

// 添加标签
$post->tags()->attach($tagId);

// 删除标签
$post->tags()->detach($tagId);

// 完全替换当前关联
$post->tags()->sync([$tagId1, $tagId2]);
sync 会自动删除未指定的中间表记录,只保留传入的 ID。

预加载(Eager Loading)

什么是 N+1 问题

以属性方式访问关联,Eloquent 每次都会发查询。这称为「延迟加载」。在循环中访问会产生 N+1 问题。
// 取全部文章 1 次查询
$posts = Post::all();

foreach ($posts as $post) {
    // 每次循环再查一次 user(N 次)
    echo $post->user->name;
}
100 篇文章会产生共 101 次查询,性能问题很大。

with() 预加载

with() 一次性把关联取回来:
// 只查 2 次
$posts = Post::with('user')->get();

foreach ($posts as $post) {
    // 不再有额外查询
    echo $post->user->name;
}
执行的 SQL 只有两条:
select * from posts

select * from users where id in (1, 2, 3, ...)

同时预加载多个关联

$posts = Post::with(['user', 'comments', 'tags'])->get();

嵌套预加载

用点号访问嵌套关联:
// 预加载 comments 及其 user
$posts = Post::with('comments.user')->get();
预加载对避免 N+1 至关重要。循环中访问关联时请养成使用 with() 的习惯。

下一步

Eloquent 入门

回顾 Eloquent 的基本 CRUD。
最后修改于 2026年7月13日