laravel 懒加载
故事背景是什么呢?
目录大家都知道吧,一般有几个层级,根据公司需求,要将目录以树的形式展示出来,为了提高访问速度,这些目录数据要一次性读取出来的。这样的话就涉及到了查询,优化查询次数是一个很关键的事情。否则的话,一个目录查询好几百次,那这个项目就不能用了。
然后数据库中各个目录之间的关联是通过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); }