Skip to content

事务和并发

free2one edited this page Jul 13, 2023 · 1 revision

事务划分

隐式

$user = new User();  
$user  
    ->setUserName('小明')  
    ->setGender(Gender::Male);  
  
$em = EntityManagerFactory::getManager();  
$em->persist($user);  
$em->flush();

调用EntityManager#flush()时将自动提交/回滚事务。

显式

$em = EntityManagerFactory::getManager();  
$em->beginTransaction();  
try {  
    $user = new User();  
    $user  
        ->setUserName('小明')  
        ->setGender(Gender::Male);  
    $em->persist($user);  
    $em->flush();  
    $em->commit();  
} catch (\Throwable $e) {  
    $em->rollBack();  
    throw $e;  
}

或以下更便捷的方式

$em = EntityManagerFactory::getManager();  
$em->wrapInTransaction(function () use ($em) {  
    $user = new User();  
    $user  
        ->setUserName('小明')  
        ->setGender(Gender::Male);  
    $em->persist($user);  
});

异常处理

当使用隐式事务并且在EntityManager#flush()期间发生异常时,事务将自动回滚并关闭EntityManager。

当使用显式事务并且发生异常时,你应该立即回滚事务,并通过调用EntityManager#close()关闭EntityManager,随后丢弃EntityManager。

如果您打算在异常发生后启动另一个工作单元,您应该使用一个新的EntityManager。

EntityManagerFactory::getManager()会保证在协程内永远返回的是相同的EntityManager未关闭实例。当检测到EntityManager已被关闭时,EntityManagerFactory会重新初始化EntityManager,并设置到当前协程上下文中。

锁支持

乐观锁

Doctrine集成了对自动乐观锁定的支持。 我们仍然通过User作为例子进行演示。首先我们修改user表结构,增加version列,并设置其类型为int unsigned。随后,我们同步修改User类,增加相应的属性及注解。

class User  
{  
    #[ORM\Version, ORM\Column(type: Types::INTEGER)]  
    private int $version;  
  
}

下面,我们模拟并发场景下对同一数据行进行修改。

$batchNum = 10;  
$channel = new Channel($batchNum);  
for ($i = 1; $i <= $batchNum; ++$i) {  
    di()->get(Coroutine::class)->create(function () use (&$channel, $i) {  
        try {  
            $em = EntityManagerFactory::getManager();  
            $user = $em->find(User::class, 1);  
            sleep(1);  
            $user->setUserName('小明_' . $i . microtime());  
            $em->flush();  
            var_dump('sucess:' . $i);
        } catch (\Doctrine\ORM\OptimisticLockException $e) {  
	        var_dump($e->getMessage() . ':' . $i);
        }  
        $channel->push($i);  
    });
}  
  
for ($len = $channel->getCapacity() + 1; --$len;) {  
    $channel->pop(-1);  
}

执行以上代码后,最终只输出一行sucess语句。

悲观锁

待补充

Clone this wiki locally