故事背景是什么呢?
目錄大家都知道吧,一般有幾個層級,根據公司需求,要將目錄以樹的形式展示出來,為了提高訪問速度,這些目錄數據要一次性讀取出來的。這樣的話就涉及到了查詢,優化查詢次數是一個很關鍵的事情。否則的話,一個目錄查詢好幾百次,那這個項目就不能用了。

然后數據庫中各個目錄之間的關聯是通過father_id來做的,具體的數據庫表設計如下:

這也就意味着,你需要根據father_id從某個目錄開始一次性的把數據全部讀出來。目錄的層級不確定。這樣的話,如果不好好思考查詢的話,將會導致查詢次數過多,給數據庫增加不必要的負擔。
能想到的最簡單的方法就是用for循環來查詢。但for循環查詢明顯的效率比較低。這個時候就需要用懶加載了。
懶加載是什么意思呢?
兩張表,目錄表和教材表。多個教材屬於一個目錄,那么利用懶加載,你就可以通過先把目錄讀出來,然后把這些與目錄有關的教材一下子讀出來完。這樣進行數據庫讀取的次數就少了。
所以我從國外的一個網站上搬來了with和load的用法,大家自行領悟吧。
Both accomplish the same end results—eager loading a related model onto the first. In fact, they both run exactly the same two queries. The key difference is that with() eager loads the related model up front, immediately after the initial query (all(), first(), or find(x), for example); when using load(), you run the initial query first, and then eager load the relation at some later point.
“Eager” here means that we’re associating all the related models for a particular result set using just one query, as opposed to having to run n queries, where n is the number of items in the initial set.
Eager loading using with()
If we eager load using with(), for example:
$users = User::with('comments')->get();
if we have 5 users, the following two queries get run immediately:
select * from `users` select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)
…and we end up with a collection of models that have the comments attached to the user model, so we can do something like $users->comments->first()->body.
“Lazy” eager loading using load()
In this approach, we can separate the two queries, first by getting the initial result:
$users = User::all();
which runs:
select * from `users`
And later, if we decide(based on some condition) that we need the related comments for all these users, we can eager load them after the fact:
if($someCondition){ $users = $users->load('comments'); }
which runs the 2nd query:
select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)
And we end up with the same result, just split into two steps. Again, we can call $users->comments->first()->body to get to the related model for any item.
Conclusion
When to use load() or with()?
load() gives you the option of deciding later, based on some dynamic condition, whether or not you need to run the 2nd query.
If, however, there’s no question that you’ll need to access all the related items, use with().
接下來我就要嘗試着去使用with和load:不得不說芳哥寫的代碼還是叼叼叼啊。
// 查詢出節點下的所有目錄,經典。 while (true) { $nextLevelNodes = new Collection(); foreach ($directories as $directory) { if ($directory->catalogue_empty) { continue; } $nextLevelNodes = $nextLevelNodes->merge($directory->childCatalogues); } if (count($nextLevelNodes) === 0) { break; } $allCatalogues = $allCatalogues->merge($nextLevelNodes); $directories = $nextLevelNodes; } // 加載文件夾下的文件,還可以。 $allCatalogues->load('childFiles.file'); // 根據父ID來索引,經典 $catalogueMaps = []; foreach ($allCatalogues as $catalogue) { $catalogueMaps[$catalogue->father_id][] = $catalogue; }
public function getCataloguesData($catalogueMaps, $parentId) { if (!isset($catalogueMaps[$parentId])) { return []; } $result = []; $directories = $catalogueMaps[$parentId]; foreach ($directories as $directory) { $treeNode = [ 'name' => $directory->name, 'type' => 'tree', 'id' => $directory->id, 'data' => [], ]; if (!$directory->catalogue_empty) { $treeNode['data'] = $this->getCataloguesData($catalogueMaps, $directory->id); } elseif (!$directory->is_empty) { $treeNode['data'] = $this->getLectureNoteData($directory->childFiles); } $result[] = $treeNode; } return $result; }
后面讀取代碼,load和with完之后,主要是通過下面的代碼獲取數據
public function inode() { return $this->belongsTo(MaterialFileInodeModel::class, 'inode_id'); }
// 可以使用 public function childFiles() { return $this->hasMany(MaterialFileModel::class, 'parent_id'); }
// 可以使用readable_size public function getReadableSizeAttribute() { if ($this->is_dir == self::IS_DIR_YES) { return '--'; } return StringUtil::formatFileSize($this->file_size); }
//last_modified public function getLastModifiedAttribute() { $time = strtotime($this->updated_at); return date('Y-m-d H:i', $time); }
