问题
我有这样的业务案例,管理员需要导入中等大小的参与者列表,最多10,000条记录。实体对4个字段(名称,姓氏,电子邮件和事件ID)具有唯一约束。将新记录更新或插入数据库的最佳方法是什么?
可能是导入列表一次,然后将更多参与者附加到excel文件,可能会在现有参与者上更改一些额外的非唯一字段,然后再次导入该文件。
我尝试过的事情
最初的想法是使用doctrine的merge()
函数,但这会导致约束违规,我的猜测是因为我的对象不包含id
第二种方法是刷新每次迭代并捕获异常,但这会导致实体管理器关闭。我试过重置它无济于事。可以尝试并持久化不抛出异常的实体,并对导致异常的实体进行另一次迭代,但这意味着发送10,000个查询
另一个想法是选择所有现有参与者,将它们放在数组集合中,并在每次迭代时在集合上调用exists()
或filter()
,但这会导致每次调用过滤函数收集元素,所以最终将是10,000 x 10,000 = 100,000,000个支票,明显可怕的表现
另一个想法是获取现有参与者并连接唯一字段并将它们放在一个散列图中,这可以在理论上起作用
我的最后一个想法是尝试以100的块为单位插入所有内容。如果插入成功,我们将移动到另一个100块。如果chunk抛出约束违规,我们将块拆分为10个更小的块尝试做同样的事情。不知道在最糟糕的情况下这会如何表现
我的代码
实体:
class Person implements Comparable
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* @var string
*
* @ORM\Column(name="surname", type="string", length=255)
*/
private $surname;
/**
* @var string
*
* @ORM\Column(name="email", type="string", length=255)
*/
private $email;
/**
* @var object
*
* @ORM\Column(name="extras", type="object")
*/
private $extras;
/**
* @ORM\ManyToOne(targetEntity="Event")
* @ORM\JoinColumn(name="event_id", referencedColumnName="id", nullable=false)
*/
private $event;
<...>
public function compareTo($other)
{
if(get_class($other) == Person::class)
{
if($other->getName() == $this->name
&& $other->getSurname() == $this->surname
&& $other->getEmail() == $this->email
&& $other->getEvent() == $this->event)
{
return true;
}
}
return false;
}
}
控制器:
public function postTicketsAction(Request $request, Event $event)
{
<...>
if($form->isValid())
{
<...>
while($iterator->valid())
{
<...>
$person = new Person();
$person
->setEvent($event)
->setName($name)
->setSurname($surname)
->setEmail($email)
->setExtras($extras)
;
$em->merge($person);
<...>
}
dump($failedPeople);
$em->flush();
}
}
答案 0 :(得分:0)
在获得新鲜空气后,我决定使用野外slu to作为检查现有记录的方法。
由于我们要检查唯一性,我们将生成一个带有连接唯一字段的字符串。但是我们还需要一个对象引用来合并到数据库中。这可以通过自定义存储库查询来解决。
这很快,因为这是一个数据库查询。
<强> PersonRepository.php 强>
<...>
public function getSlugs(Event $event)
{
$qb = $this->getEntityManager()->getRepository('AppBundle:Person')->createQueryBuilder('p');
$qb->select('CONCAT(p.name, \'-\', p.surname, \'-\', p.email, \'-\', IDENTITY(p.event)) slug, p obj')
->where($qb->expr()->eq('p.event', $event->getId()))
;
$res = $qb->getQuery()->getResult();
return [
'slugs' => array_column($res, 'slug'),
'objects' => array_column($res, 'obj')
];
}
<...>
接下来我们将其实现到控制器
中<强> PersonController.php 强>
public function postPeopleAction(Request $request, Event $event)
{
<...>
// Handle file upload and iterator creation
<...>
$existingPeople = $em->getRepository('AppBundle:Person')->getSlugs($event);
while($iterator->valid())
{
$row = $iterator->current()->getRowIndex();
$name = $sheet->getCell($requiredColumns['name'] . $row)->getValue();
$surname = $sheet->getCell($requiredColumns['surname'] . $row)->getValue();
$email = $sheet->getCell($requiredColumns['email'] . $row)->getValue();
$search = "$name-$surname-$email-$eventId";
$indexOfPerson = array_search($search, $existingPeople['slugs']);
// person already exists
if($indexOfPerson > -1)
{
$person = $existingPeople['objects'][$indexOfPerson];
$person->setExtras($extras);
$em->merge($person);
}
// this is a new person
else
{
$person = new Person();
$person
->setEvent($event)
->setName($name)
->setSurname($surname)
->setEmail($email)
->setExtras($extras)
;
$em->persist($person);
}
$iterator->next();
}
$em->flush();
}