重要
仅当您遇到使用延迟加载的缓慢页面时,此答案才有意义。对于更常见的操作(仅加载一个实体,加载“静态页面”或缓存的页面等),您可以继续使用教义提供的内置函数
好吧,让我们分析一些你可以遵循的不同方法
1 - findOneById()
进入控制器,您选择遵循更“常见”和“快速”的方式进行:使用预先存在的findOneById() 方法。太棒了。
让我们检查一下性能
-
完成的查询数:完成了三个查询,因为我们需要一个来检索
Lodging 对象,第二个来获取当前关联的 Currency(延迟加载)和一个获取所有 Currency 实体(因为您可以从一种货币更改为另一种)
-
页面加载时间:约 500 毫秒
-
内存使用量:大约 32MB
2 - 编写自定义存储库方法
2.1 “基本”DQL 存储库函数
public function findLodgingById($id)
{
$lodging = null;
$q = $this->createQueryBuilder('lodging')
->select('lodging')
->where('lodging.id = :id')
->setParameter('id', $id)
->getQuery();
$lodging_array = $q->getResult(); //will not fetch a single object but array
if ($lodging_array) {
$lodging = reset($lodging_array);
}
return $lodging;
}
让我们检查一下性能
-
已完成的查询数:这对您来说应该不足为奇,但是...已完成的查询数始终为 3!当然,除了 findOneById() 之外,你什么也没做(甚至可能是最糟糕的方式!)。你又在利用延迟加载了。
-
页面加载时间:大约 500 毫秒。加载时间没有改变
-
内存使用量:大约 34MB。内存使用量增加了 6.25 %(由于数组?)
2.2 DQL 与 JOIN
public function findLodgingById($id)
{
$lodging = null;
$q = $this->createQueryBuilder('lodging')
->select('lodging')
->leftJoin('lodging.currency', 'currency')
->where('lodging.id = :id')
->setParameter('id', $id)
->getQuery();
$lodging_array = $q->getResult(); //will not fetch a single object but array
if ($lodging_array) {
$lodging = reset($lodging_array);
}
return $lodging;
}
让我们检查一下性能
-
查询次数:查询次数不变!但为什么?我们明确告诉教条2 加入
currency 实体,但它似乎忽略了该指令。答案是我们也没有选择货币实体,因此教条2 将再次使用延迟加载工具。
-
页面加载时间:大约 500 毫秒。加载时间没有改变 2.1
-
内存使用量:大约 34MB。内存使用量与 2.1 相比没有变化
2.3 让我们尝试更好的方法:加入货币选择
public function findLodgingById($id)
{
$lodging = null;
$q = $this->createQueryBuilder('lodging')
->select('lodging', 'currency')
->leftJoin('lodging.currency', 'currency')
->where('lodging.id = :id')
->setParameter('id', $id)
->getQuery();
$lodging_array = $q->getResult(); //will not fetch a single object but array
if ($lodging_array) {
$lodging = reset($lodging_array);
}
return $lodging;
}
让我们检查一下性能
-
查询次数:查询次数终于减少了!我们达到两个查询而不是三个。什么查询消失了?关联(当前)货币的延迟加载已消失,但当然,您必须获取所有可能的货币。
-
页面加载时间:大约 350 毫秒。
-
内存使用量:大约 34MB。内存使用没有改变
最终解决方案 (?)
public function findLodgingById($id)
{
$lodging = null;
$q = $this->createQueryBuilder('lodging')
->select('lodging')
->where('lodging.id = :id')
->setParameter('id', $id)
->getQuery();
$q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
$lodging_array = $q->getResult(); //will not fetch a single object but array
if ($lodging_array) {
$lodging = reset($lodging_array);
}
return $lodging;
}
与其他解决方案相比,$q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); 行代码似乎可以节省时间和内存。
-
完成的查询次数:当然是两个
-
页面加载时间:大约 340 毫秒。
-
内存使用量:大约 32MB。
请注意
此解决方案不允许您更改关联的 (Currency) 实体,因为它将与 Query::HINT_FORCE_PARTIAL_LOAD, true 绑定
评论
结果似乎对页面加载时间有好处(内存使用当然不会改变),虽然性能似乎只是“稍微”好一点,但您不应该只关注结果:这里我们认为一个例子,只是一个简单的 sn-p 代码,只有一个延迟加载操作:考虑一个实体(或者,最糟糕的是,很多实体,比如带有相关 cmets 的博客文章),它将为每个获取的实体执行错误(*)延迟加载和管理:单个页面甚至可以达到 50-70 个查询(当然,在这种情况下,您可以轻松地注意到由于“单个”查询而带来的性能优势)
(*) 为什么我说错了?因为如果您可以将对象的获取逻辑迁移到其他地方,或者如果您已经知道您需要什么实体/属性,那么您的内容不是动态,您可以在使用前知道它们,延迟加载不仅无用,而且有害。
相反,如果您在“编写代码时”不知道您需要哪些属性或实体,那么延迟加载当然可以为您节省内存(和时间)浪费在无用的对象/关联上。
最后的想法
最好“浪费”一些时间来编写使用“内置”查询的 DQL 查询(这似乎很愚蠢)。此外,您应该使用数组(而不是对象)进行只读操作(列出无法修改的元素),更改 getResult() 方法调用如下:getResult(Doctrine\ORM\Query::HYDRATE_ARRAY);。这将改变“默认”值(HYDRATE::OBJECT)