
本文详解如何在 Laravel 9 中正确使用 Eager Loading 预加载关联模型,彻底避免因 {{ $task->job->title }} 触发的 N+1 查询问题,并指出 Blade 模板中访问关系属性的常见误区。
本文详解如何在 laravel 9 中正确使用 eager loading 预加载关联模型,彻底避免因 `{{ $task->job->title }}` 触发的 n+1 查询问题,并指出 blade 模板中访问关系属性的常见误区。
在 Laravel 应用中,当一个任务(Task)需显示其所属职位(Job)的标题(title)时,若未正确预加载关联数据,极易触发经典的 N+1 查询问题——即每获取一个 Task 后,又单独执行一次 SQL 查询去加载其 job,导致数据库请求激增,性能严重下降。
你当前的代码逻辑基本正确:
- Task 正确定义了 belongsTo(Job::class) 关系;
- Planner::getTask() 方法中调用了 ->with('job');
- Blade 模板中尝试通过 {{ $task->job->title }} 访问。
但问题根源在于:你在视图中使用的 $task 是一个集合(Collection),而非单个模型实例。
? 关键错误分析:
Task::where(...)->with('job')->get() 返回的是 Illuminate\Database\Eloquent\Collection(即多个 Task 的集合),而你在 Blade 中写的是:
<div>{{ $task->job->title }}</div>
这行代码隐含两个致命假设:
- $task 是单个 Task 模型(实际是 Collection);
- 即便 $task 是单个模型,$task->job 是延迟加载的关系对象(HasOneThrough/BelongsTo 实例),而非已加载的 Job 模型 —— 但 ->with('job') 已预加载,所以该访问本身不会触发新查询;真正触发 N+1 的,是后续对集合中每个 $task 重复访问 ->job->title 时未统一预加载,或控制器传参方式不匹配。
✅ 正确解决方案如下:
1. 确保控制器传递的是预加载后的集合,并在 Blade 中遍历使用
修改 PlannerController@index 和视图逻辑:
// App/Http/Controllers/PlannerController.php
public function index(Request $request)
{
$tasks = Planner::getTask(1, '2022-05-26'); // 注意:原示例日期 '2022-26-05' 格式错误,应为 'Y-m-d'
return view('pages.planner.index', compact('tasks'));
}
// App/Models/Planner.php
public static function getTask($user, $date)
{
return Task::where('user_id', $user->id)
->where('date', $date) // ✅ 修复变量名:原代码中使用了未定义的 $day
->with('job') // ✅ 正确预加载
->get(); // ✅ get() 返回 Collection,无需 all()
}
对应更新 Blade 模板(resources/views/pages/planner/index.blade.php):
@foreach ($tasks as $task)
<div>
<strong>Task ID:</strong> {{ $task->id }}<br><strong>Job Title:</strong>
@if ($task->job)
{{ $task->job->title }}
@else
<span class="text-muted">No job assigned</span>
@endif
</div>
@endforeach
⚠️ 注意:$task->job 是预加载后的 Job 模型实例(或 null),可安全访问 ->title;绝不可写作 $task->job()->title(带括号会返回关系构造器,报错)或 $task->job->title()(方法不存在)。
2. 进阶优化:使用 with() 的闭包自定义选择字段(减少内存开销)
若 Job 表字段较多,仅需 title,可限制查询字段:
->with(['job' => function ($query) {
$query->select('id', 'title'); // ✅ 必须包含外键字段(如 id),否则关联失效
}])
3. 验证是否生效
启用 Laravel Debugbar 后,观察「Queries」标签页:
- 若只看到 *1 条 `SELECT FROM tasks ...+ 1 条SELECT ... FROM jobs WHERE id IN (...)`**,说明预加载成功;
- 若出现多条 SELECT * FROM jobs WHERE id = ?,则仍存在 N+1,需检查是否在循环中意外调用了未预加载的关系(如 ->job->department->name 但未 with('job.department'))。
总结
| 问题环节 | 正确做法 |
|---|---|
| Eager Loading 声明 | ->with('job')(关系名,非完整命名空间) |
| 模型关系定义 | Task::job() 返回 belongsTo(),确保 job_number 字段与 Job::number 匹配(必要时指定外键) |
| 控制器数据传递 | 使用 compact('tasks') 或 ['tasks' => $tasks],避免混淆单数/复数变量名 |
| Blade 访问方式 | {{ $task->job->title }}(无括号!$task->job() 是错误的)且必须配合 @foreach 遍历集合 |
| 空值防护 | 始终用 @if ($task->job) 判断,避免 Trying to get property 'title' of null 错误 |
遵循以上实践,即可在 Laravel 9 中彻底消除该场景下的 N+1 查询,提升接口响应速度与数据库负载表现。











