使用symfony导入大量数据
我们在开发过程中往往会遇到批量导入excel等数据,如果每一次导入的数据量很少,那么只需要按普通方法 写代码即可。
可时如果我们需要一次性导入大量数据的话,普通写法可能会导致内存溢出或者其它问题, 下面我们针对这种情况进行一个讲解。
首先,读取超大excel文件推荐两个库:
1、league/csv
该库只能读取csv文件,并且要求带BOM,所以对中文不太友好。优点是只需要装库即可
2、xlswriter
该库可以读取csv和xlsx,读取速度快并且占用资源少,缺点是需要手动安装php扩展
处理超大数据时,建议使用游标读取,一行一行的处理。
根据doctrine官方推荐的批量插入方法推荐如下写法:
private function insertBooks()
{
$result = [1, 2, 3, 4, 5, 6, 7, 8, 9];
foreach ($result as $key => $item) {
$name = '书' . $item;
$book = new Book();
$book->setName($name);
$this->em->persist($book);
if ($key % 2 === 0) {
$this->em->flush();
$this->em->clear();
}
}
$this->em->flush();
}
接下来我们假设有一个分类实体(Category)和一个书的实体(Book)
假设分类实体已经全部生成了数据,我们在导入书时,需要根据条件查询分类,所以我们需要修改一下代码:
private function insertBooks()
{
$result = [1, 2, 3, 4, 5, 6, 7, 8, 9];
foreach ($result as $key => $item) {
$name = '书' . $item;
$book = new Book();
$category = $this->em->getRepository(Category::class)->findOneBy(['id' => $item]);//这里只是举例,并非只是通过id查找
$book
->setName($name)
->setCategory($category);
$this->em->persist($book);
if ($key % 2 === 0) {
$this->em->flush();
$this->em->clear();
}
}
$this->em->flush();
}
你可以看到我们在每一次循环里都查询了一次分类,如果你需要循环十万次呢? 这肯定是不推荐的方法,太浪费资源 所以你肯定会按我下面的方法修改代码:
private function insertBooks()
{
$categories = [];
$result = $this->em->getRepository(Category::class)->findAll();
foreach ($result as $item) {
$categories[$item->getId()] = $item;
}
$result = [1, 2, 3, 4, 5, 6, 7, 8, 9];
foreach ($result as $key => $item) {
$name = '书' . $item;
$book = new Book();
$category = $categories[$item];
$book
->setName($name)
->setCategory($category);
$this->em->persist($book);
if ($key % 2 === 0) {
$this->em->flush();
$this->em->clear();
}
}
$this->em->flush();
}
你想的没错,我们先在循环外把所有分类取出来,然后在循环内根据key去查找对应的对象就行,运行后你就会得到一个错误:
A new entity was found through the relationship 'App\Entity\Book#category' that was not configured to cascade persist operations for entity: 分类1. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"})
明明分类已经在数据库了,为什么还会出现这个错误呢? 因为我们清除了一次$this->em->clear();
要解决这个问题其实很简单,按下面代码来修改:
private function insertBooks()
{
$categories = [];
$result = $this->em->getRepository(Category::class)->findAll();
foreach ($result as $item) {
$categories[$item->getId()] = $item->getId();
}
$result = [1, 2, 3, 4, 5, 6, 7, 8, 9];
foreach ($result as $key => $item) {
$name = '书' . $item;
$book = new Book();
$category = $this->em->getReference(Category::class, $categories[$item]);
$book
->setName($name)
->setCategory($category);
$this->em->persist($book);
if ($key % 2 === 0) {
$this->em->flush();
$this->em->clear();
}
}
$this->em->flush();
}
首先我们在$categories不存整个对象,而只存id,然后每一次循环我们都通过$this->em->getReference()去取对象的引即可。
你也许会疑惑,仅仅是这样吗?取对象的引用不会在数据库里循环?是的,你可以在调试栏查看sql数量,完全不会出现循环查询的sql, 至此,整个流程就完成了