
本文详细阐述在apiato框架中,如何通过eager loading(预加载)技术解决n+1查询问题,并结合transformer进行关系数据的有效序列化。我们将探讨`l5-repository`的使用、请求参数处理以及transformer中`defaultincludes`与`*ailableincludes`的配置,确保数据库层面的性能优化与api响应的灵活输出同步进行。
在开发API时,当我们需要获取一个主资源及其关联的多个子资源时,如果不采用适当的优化策略,往往会遇到臭名昭著的“N+1查询问题”。例如,在获取一个用户列表时,如果每个用户的订阅信息都需要单独查询,那么对于N个用户,将执行1次查询获取用户列表,再执行N次查询获取每个用户的订阅,总计N+1次数据库查询。这会严重影响API的性能和响应速度。
为了解决这一问题,Eager Loading(预加载)技术应运而生。通过预加载,我们可以在获取主资源时,一次性地将所有关联的子资源也查询出来,从而将N+1次查询减少到2次(一次主资源,一次所有关联子资源),显著提升数据库操作效率。
Apiato框架基于Lar*el,并集成了l5-repository(一个数据仓库模式实现)和Fractal Transformers(用于数据转换和序列化)。理解这三者如何协同工作是解决关系数据加载问题的关键:
在Apiato中,通常使用?include=relationName这样的URL查询参数来触发Transformer的*ailableIncludes。而?with=relationName参数则主要用于指示l5-repository进行数据库层面的预加载。两者虽有区别,但在某些配置下,with参数也可能被Apiato内部处理并映射到Transformer的include逻辑。
为了确保l5-repository在查询数据时能够执行预加载,我们需要在请求类中明确地传递with参数。这可以通过在请求类的prepareForValidation方法中添加参数来实现。
示例:强制在请求中添加with参数
// app/Containers/User/UI/API/Requests/GetAllUsersRequest.php
namespace App\Containers\User\UI\API\Requests;
use App\Ship\Parents\Requests\Request as ParentRequest;
class GetAllUsersRequest extends ParentRequest
{
/**
* Define which roles and/or permissions has access to this request.
*/
protected array $access = [
'permissions' => 'list-users',
'roles' => '',
];
/**
* Id's that needs decoding before applying the validation rules.
*/
protected array $decode = [
// 'id',
];
/**
* Defining the URL parameters (`/stores/{id}`) allows applying validation rules
* on them and selecting appropriate users (e.g., `id` would be selected with the getById() methods).
*/
protected array $urlParameters = [
// 'id',
];
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
// 'id' => 'required|integer|exists:users,id',
];
}
/**
* Prepare the data for validation.
* 在这里强制添加 'with' 参数,确保l5-repository进行数据库预加载
*/
public function prepareForValidation()
{
// 将 'with=subscription' 添加到请求参数中
// 这会影响l5-repository在获取数据时调用 Eloquent 的 with() 方法
request()->request->add(['with' => 'subscription']);
request()->query->add(['with' => 'subscription']);
}
}通过上述配置,无论API请求URL是否包含?with=subscription,l5-repository在执行查询时都会尝试预加载subscription关系。这解决了N+1查询问题,确保了数据库查询的效率。
仅仅在数据库层面进行预加载是不够的。如果Transformer没有被告知要序列化这些关系数据,它们将不会出现在最终的API响应中。当从Transformer中移除defaultIncludes后,关系将不再自动包含。为了实现按需包含,我们需要使用*ailableIncludes和对应的include[RelationName]方法。
千鹿Pr助手
智能Pr插件,融入众多AI功能和海量素材
128
查看详情
示例:配置UsersTransformer以按需包含subscription
// app/Containers/User/UI/API/Transformers/UserTransformer.php
namespace App\Containers\User\UI\API\Transformers;
use App\Containers\User\Models\User;
use App\Containers\Subscription\UI\API\Transformers\SubscriptionTransformer; // 假设有 SubscriptionTransformer
use App\Ship\Parents\Transformers\Transformer;
use League\Fractal\Resource\Item;
class UserTransformer extends Transformer
{
/**
* @var array
*/
protected array $resourceKey = ['users', 'user'];
/**
* 移除 defaultIncludes,使关系不再默认包含
* public array $defaultIncludes = ['subscription'];
*/
/**
* 定义可按需包含的关系。
* 通常通过 URL 参数 ?include=subscription 来触发。
* Apiato 也会在某些情况下将 ?with=... 映射到这里。
*/
public array $*ailableIncludes = [
'subscription',
];
public function transform(User $user): array
{
return [
'id' => $user->getHashedKey(),
'name' => $user->name,
'email' => $user->email,
'email_verified_at' => $user->email_verified_at,
'created_at' => $user->created_at,
'updated_at' => $user->updated_at,
'readable_created_at' => $user->created_at->diffForHumans(),
'readable_updated_at' => $user->updated_at->diffForHumans(),
];
}
/**
* 定义如何包含 'subscription' 关系。
* 当请求中包含 ?include=subscription (或 ?with=subscription,如果被映射) 时,此方法会被调用。
*/
public function includeSubscription(User $user): ?Item
{
// 关键:检查关系是否已加载,避免N+1查询
if ($user->relationLoaded('subscription') && $user->subscription) {
return $this->item($user->subscription, new SubscriptionTransformer());
}
return null;
}
}解释:
现在,结合数据库层面的预加载和Transformer的配置,我们可以通过以下URL请求来获取用户及其订阅信息:
API请求示例:
GET v1/users?include=subscription
当此请求到达Apiato时:
注意事项:
通过以上步骤,你可以在Apiato框架中高效地处理关系数据,既解决了N+1查询问题,又保持了API响应的灵活性和可控性。
以上就是Apiato中高效处理关系数据:预加载与Transformer配置指南的详细内容,更多请关注php中文网其它相关文章!
相关文章:
PyTorch模型训练效果不佳?深入剖析常见错误与调试技巧
在J*a中如何在J*a中使用异常机制记录错误日志_异常日志实践经验
深入理解J*a链表中的IPosition接口与使用
c++如何实现一个简单的ECS框架_c++数据驱动设计与游戏开发
C++ explicit关键字防止隐式转换_C++构造函数安全规范
为什么我的微信朋友圈看不到别人的更新_微信朋友圈更新显示异常解决方法
CSS Grid如何控制元素对齐_align-items与justify-items组合使用
随机参数递归函数的基准调用次数与时间复杂度探究
抖音网页版平台入口 抖音网页版官网在线访问教程
汽水音乐网页版使用入口_汽水音乐电脑版播放指南
汽水音乐在线解析 汽水音乐在线解析入口
sublime如何配置Go语言开发环境_sublime搭建Golang编译运行系统
机器学习中对数变换预测结果的反向还原
机构:以往存储涨价周期小米利润率实际上有所改善 能转嫁给消费者等
Win11怎么开启卓越性能模式 Win11电源选项启用高性能释放硬件潜力【方法】
Angular响应式表单:实现提交后表单及按钮的禁用与只读化
J*aScript打印功能_j*ascript输出控制
LINUX怎么设置定时任务_LINUX crontab配置教程
德邦快递查询平台 德邦快递物流信息查询入口
俄罗斯Yandex搜索引擎入口_Yandex官网免登录一键访问
Lar*el DB::listen 事件中的查询执行时间单位解析
msn官网入口地址手机版 msn官方网站手机最新链接
漫蛙Manwa2官网入口地址分享 漫蛙漫画PC版永久访问通道
Flexbox布局实践:实现粘性导航栏与底部固定页脚
Python自定义类排序:解决lambda键值访问TypeError的实践指南
Composer如何解决json扩展缺失的错误
outlook中文官网入口地址 outlook官方中文版直达首页链接
京东单号查询入口_京东快递订单追踪入口
俄罗斯方块最新版入口 俄罗斯方块在线玩官网入口
AO3网页版合集入口 Archive of Our Own同人作品浏览指南
PHP表单隐藏域数据传递:常见问题与最佳实践
魅族17怎样用浏览器译外语网页_iPhone魅族17浏览器译外语网页【即时翻译】
基于多条件高效更新SQL表:利用CASE表达式优化业务逻辑
响应式CSS Grid布局:优化网格项在小屏幕下的堆叠与宽度适配
单射、满射与双射的关系 一文理清所有逻辑
Win11怎么开启省电模式_Win11电池节电模式自动开启
Node.js 中使用 node-cron 实现定时 API 数据抓取与处理
Go语言实现持久化与原子性文件存储的教程
PHP表单提交消息延迟显示:Post-Redirect-Get模式深度解析与实践
响应式容器内容自动缩放与宽高比维持教程
手机屏幕碎了但能正常使用怎么办 手机外屏碎裂的修复建议
PHP字符串中复杂变量插值的最佳实践与语法解析
yy漫画网页版官方入口_yy漫画官网登录页面链接
如何高效处理PHP中的Excel数据导入导出?PortPHP/Spreadsheet助你轻松搞定!
微信网页版官方快速登录入口 微信网页版网页版账号直达
Go RPC HTTP服务正确实现与常见陷阱解析
汽水音乐车机版8.9下载 汽水音乐车机版8.9版本安装入口
html怎么运行外部js文件中的函数_运html外js文件函数法【技巧】
漫蛙2在线漫画入口 漫蛙正版漫画网页版直达
腾讯QQ邮箱官方网站_QQ邮箱网页版在线登录