使用symfony导入大量数据

Mario Sanchez

20 people read
Thumbnail

使用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, 至此,整个流程就完成了

About Me

我是一位精通 Symfony 框架和 API Platform 的开发者,擅长构建高效、可扩展的 Web 应用程序和 API。 此外,我还具备 PrestaShop 模块开发经验,能够为您的电商平台定制功能,满足特定业务需求。