优化
您可以在不使用Doctrine的结果缓存的情况下优化您的过程:
首先创建一个年份到其ID的映射,如下所示:
$yearsMap = array();
$q = $em->createQuery('SELECT y.id, y.year_name FROM Entities\Year y');
foreach ($q->getScalarResult() as $row) {
$yearsMap[$row['year_name']] = $row['id'];
}
同时创建一个部门到其ID的映射表,以及一个分区到其ID的映射表。这将导致3个轻量级查询。最好的放置代码的位置是在(自定义)仓库中。
接下来,您可以运行循环,但是像这样“获取”实际实体:
$year = $this->em->getReference('Entities\Year', $yearsMap[$this->year[$i]]);
$department = $this->em->getReference('Entities\Department', $departmentsMap[$this->branch[$i]]);
$division = $this->em->getReference('Entities\Division', $divisionsMap[$this->division[$i]]);
我说“获取”,因为
getReference()
实际上创建了一个代理(除非它已经被实体管理器加载,但在这种情况下可能不是)。该代理尚未加载,因此此处不执行任何查询。
您的其余代码不需要更改。
现在调用flush()
时,Doctrine将仅加载每个不同的年份/部门/分部一次。这可能仍然导致一些查询,取决于使用多少个不同的年份/部门/分部。因此,如果所有100名学生使用不同的年份/部门/分部,则最终会产生403个查询(3个用于映射,300个用于加载代理,100个用于插入学生)。但是,如果所有100名学生使用相同的年份/部门/分部,则最终只会产生106个查询(3个用于映射,3个用于加载代理,100个用于插入学生)。
另一种优化方式
另一种方法是使用您收集的名称来获取所需的所有实体:
$q = $em->createQuery('SELECT y FROM Entities\Year y INDEX BY y.year_name WHERE y.year_name IN(:years)');
$q->setParameter('years', $yearNames);
$yearsMap = $q->getResult();
现在你只需要一个查询就可以获得所有所需的年份实体。部门和分区也可以采用相同的方法。
还要注意DQL语句中的INDEX BY
:这将确保你得到一个以year_name
为键,实体为值的数组。你可以直接在循环中使用它,如下所示:
$year = $yearsMap[$this->year[$i]];
$department = $departmentsMap[$this->branch[$i]];
$division = $divisionsMap[$this->division[$i]];
The end result for 100 students will always be 103 queries (3 for the maps, 100 for inserting students).
Cache
当您需要经常运行此循环并且它会对数据库造成压力时,最好使用Doctrine的
result cache。但是需要注意一些事项:
getReference()
尚不支持结果缓存,而且结果缓存不会自动使用。因此,建议您在存储库中放置类似以下内容的代码:
public function findOneYearByName($name)
{
$q = $em->createQuery('SELECT y FROM Entities\Year y WHERE y.year_name = :year');
$q->setParameter('year', $name);
$q->useResultCache(true);
return $q->getSingleResult();
}
您可能想要配置结果缓存,请参见文档。
另一个需要注意的是,结果缓存将缓存从数据库中获取的结果,在其被填充之前。因此,即使使用结果缓存,实际的实体也会每次都被填充。因此,我仍然建议使用映射,但实现方式略有不同:
$yearsMap = array();
$departmentsMap = array();
$divisionsMap = array();
forloop (...):
if (!isset($yearsMap[$this->year[$i]])) {
$yearsMap[$this->year[$i]] = $this->em->getRepository('Entities\Year')->findOneYearByName($this->year[$i]);
}
if (!isset($departmentsMap[$this->branch[$i]])) {
$departmentsMap[$this->branch[$i]] = $this->em->getRepository('Entities\Department')->findOneDepartmentByName($this->branch[$i]);
}
if (!isset($divisionsMap[$this->division[$i]])) {
$divisionsMap[$this->division[$i]] = $this->em->getRepository('Entities\Division')->findOneDivisionByName($this->division[$i]);
}
$year = $yearsMap[$this->year[$i]];
$department = $departmentsMap[$this->branch[$i]];
$division = $divisionsMap[$this->division[$i]];
这样可以确保每个不同的年份/部门/分区只会被一次注入水分。
PS:对于“另一种优化方式”,使用结果缓存效果不佳,因为年份/部门/分区名称在每次运行循环时可能是不同的。随着名称的变化,查询也会发生变化,无法使用缓存的结果。
DBAL
问:我可以在插入数据时直接根据名称获取年份、部门和分区的ID吗?
答:您可以这样做,但您只能使用DBAL而不是ORM。基本上是这样的:
$connection = $em->getConnection();
$statement = $conn->executeQuery('insert query', array('parameter1', 'etc'));
$statement->execute();
我认为这种方法并不会更加高效,因为MySQL(或者你使用的其他供应商)仍然会为每个插入执行那三个(子)查询,只是这些查询不会“通过网络传输”。而且你也无法从ORM中获得任何帮助,例如管理关联等。
不过,你可以在
这里找到有关该主题的所有内容。