我在開發API的時候,對於如何拿到關聯資料返回數據我覺得有一些東西可以分享。
以下是我其中一個功能,他可以讓使用者查看目前所有新增的寶可夢。
public function index()
{
// 透過JWT取得當前登入的用戶
$user = auth()->user();
$pokemons = $user->pokemons()->with(['user', 'ability', 'nature', 'race'])->get();
return PokemonResource::collection($pokemons);
}
總之我是透過和寶可夢表的關連去拿到數據,而我這裡先透過with預加載的方式去解決n+1問題,也就是在我的resource裡面去取的關聯資料的時候,我不用每次要去取得一隻寶可夢的關聯資料就要取一次。
n+1問題
我的理解就是:
假設我們有一個**Author
(作者)表格和一個Book
(書籍)表格。每位Author
都有多本Book
**。
現在,我們想從資料庫中查詢所有的作者和他們的書籍。
如果使用簡單的查詢方法,可能會這麼做:
Author
**。Author
,查詢其所寫的Book
**。這樣的話,假如有10位**Author
,我們就需要執行1次查詢Author
的操作,再加上10次查詢Book
**的操作,總共是11次查詢。
那這樣會有什麼問題呢?
假設今天資料庫是做全表搜尋(也就是從頭到尾掃過一遍去找你的資料),那你對他請求十次。他就要掃十次。
但如果你今天一次說我要找作者id 1-10的書籍
那他可能只要全表搜尋一次這十個作者的書籍然後一次返回就好,效率上就會有差。
但現在有一個問題是,我的pokemon和skills之間是沒有建立關聯的,所以我沒有辦法做愈加載的動作,於是我想了幾個方案:
1.先在進入到resource之前把skill資料都取得後再當作參數傳入resource,但後來我是沒想到要如何把這個東西傳入到resource然後再一一對應到每隻寶可夢所以沒有走這條路。
2.使用cache:
概念上就是,我先去一次把所有技能名稱及id取出來,然後放到cache裡,接者在和沒次循環的寶可夢的技能id陣列去比對,就可以找到對應的技能名稱了。
此方法好處是,我今天只需要在第一次對資料庫做一次搜尋,而之後我cache的某個key裡面有值他就不會再去對資料庫做搜索。
程式碼如下:
$minutes = 60; // 設定 cache 60 分鐘後過期
$allSkills = Cache::remember('all_skills', $minutes, function () {
return Skill::all();
});
$allSkillsArray = $allSkills->pluck('name', 'id')->toArray();
// 取得 $this->skills 中指定的技能名稱
$selectedSkillNames = array_intersect_key($allSkillsArray, array_flip($this->skills));
// 如果需要,將其重新整理為索引陣列
$selectedSkillNames = array_values($selectedSkillNames);
其實這種方法我也不確定是不是一個最優解,但就是目前能想到的一個方法,就分享一下。