Laravel:使用 try...catch 和 DB::transaction()

Posted

技术标签:

【中文标题】Laravel:使用 try...catch 和 DB::transaction()【英文标题】:Laravel: Using try...catch with DB::transaction() 【发布时间】:2014-05-19 08:48:32 【问题描述】:

我们都使用DB::transaction() 进行多个插入查询。这样做时,应该将try...catch 放在里面还是包裹起来?如果出现问题,事务会自动失败,是否还需要包含try...catch

try...catch 包装事务的示例:

// try...catch
try 
    // Transaction
    $exception = DB::transaction(function() 

        // Do your SQL here

    );

    if(is_null($exception)) 
        return true;
     else 
        throw new Exception;
    


catch(Exception $e) 
    return false;

相反,DB::transaction() 包装了一个 try...catch:

// Transaction
$exception = DB::transaction(function() 
    // try...catch
    try 

        // Do your SQL here

    
    catch(Exception $e) 
        return $e;
    

);

return is_null($exception) ? true : false;

或者只是一个没有 try...catch 的事务

// Transaction only
$exception = DB::transaction(function() 

    // Do your SQL here

);

return is_null($exception) ? true : false;

【问题讨论】:

【参考方案1】:

我决定回答这个问题,因为我认为它可以使用比复杂的 try-catch 块更简单的语法来解决。 Laravel 文档对此主题非常简短。

您可以像这样使用 DB::transaction()... 包装器,而不是使用 try-catch:

// MyController.php
public function store(Request $request) 
    return DB::transaction(function() use ($request) 
        $user = User::create([
            'username' => $request->post('username')
        ]);

        // Add some sort of "log" record for the sake of transaction:
        $log = Log::create([
            'message' => 'User Foobar created'
        ]);

        // Lets add some custom validation that will prohibit the transaction:
        if($user->id > 1) 
            throw AnyException('Please rollback this transaction');
        

        return response()->json(['message' => 'User saved!']);
    );
;

您应该看到,在此设置中,用户和日志记录不能相互独立。

关于上述实现的一些注意事项:

确保return 处理任何事务,以便您可以使用在其回调中返回的response() 作为控制器的响应。 如果您希望事务回滚(或者有一个自动为您抛出异常的嵌套函数,就像 Eloquent 中的任何 SQL 异常一样),请确保 throw 异常。 idupdated_atcreated_at 和任何其他字段在创建 $user 对象后可用(至少在此事务期间)。事务将通过您拥有的任何创建逻辑运行。但是,当SomeCustomException 被抛出时,整个记录都会被丢弃。 id 的自动增量列会在事务失败时增加。

在 Laravel 5.8 上测试

【讨论】:

【参考方案2】:

我正在使用 Laravel 8,您应该将事务包装在 try-catch 中,如下所示:

try 
    DB::transaction(function () 
        // Perform your queries here using the models or DB facade
    );

catch (\Throwable $e) 
    // Do something with your exception

【讨论】:

【参考方案3】:

我知道这可能以前得到了很好的回答,但我想给予我的支持:D。

我有时会这样做。

try 
    DB::transaction(function () use (/* place here extra variables if you need */) 
        throw new Exception("Hubo un error en la transacción");
    );
    // If no errors, you can continue with your common workflow.
 catch (Exception $e) 
    // You can check here because the transaction will auto rollback and then will throw an exception.
    return $e->getMessage();

【讨论】:

【参考方案4】:

在 laravel 8 中,你可以在 try-catch 中使用 DB::transaction。 例如:

try
    DB::transaction(function() 
        // do anything
    );

catch()
    // do anything

如果每个查询在尝试时都失败,则运行 catch 块。

【讨论】:

【参考方案5】:

您可以将事务包装在 try..catch 上,甚至可以反转它们, 这是我以前在 laravel 5 中使用的示例代码,如果您深入查看 DB:transaction() 中的 Illuminate\Database\Connection ,那就像您编写手动事务一样。

Laravel 事务

public function transaction(Closure $callback)
    
        $this->beginTransaction();

        try 
            $result = $callback($this);

            $this->commit();
        

        catch (Exception $e) 
            $this->rollBack();

            throw $e;
         catch (Throwable $e) 
            $this->rollBack();

            throw $e;
        

        return $result;
    

因此您可以像这样编写代码,并处理您的异常,例如通过 Flash 将消息扔回您的表单或重定向到另一个页面。记住闭包内的 return 在 transaction() 中返回,所以如果你返回 redirect()->back() 它不会立即重定向,因为它返回到处理事务的变量。

包装交易

$result = DB::transaction(function () use ($request, $message) 
   try

      // execute query 1
      // execute query 2
      // ..

      return redirect(route('account.article'));

    catch (\Exception $e) 
       return redirect()->back()->withErrors(['error' => $e->getMessage()]);
    
 );

// redirect the page
return $result;

那么替代方法是抛出布尔变量并在事务函数之外处理重定向,或者如果您需要检索事务失败的原因,您可以从$e->getMessage() 内部catch(Exception $e)... 获取它

【讨论】:

我使用了没有 try-catch 块的事务,效果也很好 @hamidrezasamsami 是的,数据库自动回滚,但有时您需要知道查询是否全部成功.. “Wrap Transaction”示例是错误的。这将始终提交,即使其中一个查询失败,因为所有异常都在事务回调中捕获。您想将 try/catch 放在 DB::transaction 之外。【参考方案6】:

如果您使用 PHP7,请在 catch 中使用 Throwable 来捕获用户异常和致命错误。

例如:

DB::beginTransaction();

try 
    DB::insert(...);    
    DB::commit();
 catch (\Throwable $e) 
    DB::rollback();
    throw $e;

如果您的代码必须与 PHP5 兼容,请使用 ExceptionThrowable

DB::beginTransaction();

try 
    DB::insert(...);    
    DB::commit();
 catch (\Exception $e) 
    DB::rollback();
    throw $e;
 catch (\Throwable $e) 
    DB::rollback();
    throw $e;

【讨论】:

DB::beginTransaction() 也可能抛出 \Exception 的事实如何?是否应该包含在 try/catch 中? 如果事务还没有开始,我们不需要回滚任何东西。此外,在catch 块中尝试回滚未启动的事务是没有好处的。因此DB::beginTransaction() 的好地方是在try 块之前。【参考方案7】:

如果您需要通过代码手动“退出”事务(无论是通过异常还是只是检查错误状态),您不应使用 DB::transaction(),而应将代码包装在 DB::beginTransaction 和 @987654324 中@/DB::rollback():

DB::beginTransaction();

try 
    DB::insert(...);
    DB::insert(...);
    DB::insert(...);

    DB::commit();
    // all good
 catch (\Exception $e) 
    DB::rollback();
    // something went wrong

请参阅transaction docs。

【讨论】:

DB::beginTransaction()DB:transaction()有什么区别? DB::transaction 接受一个匿名函数以使 DB 语句在事务中运行,DB::beginTransaction() 要求将 DB 语句写在调用的“旁边”(根据上面的示例),然后一个最终的DB::commit()DB::rollback() 来完成交易。 简单问题:如果您在异常后不回滚,或者如果您不捕获异常,会发生什么?脚本结束后自动回滚? 不幸的是我不知道,但是是的,我会想象事务保持打开状态,愉快地吞下更多的数据库插入/更新,然后最终当应用程序结束时,整个事情都回滚了。拼凑一个快速测试脚本来尝试它会很容易。您可能会发现“事务未退出”样式异常以及自动回滚。 @HengSopheak 这个问题是关于 Laravel 4 数据库的,所以很可能我的答案对于 5.3 不再正确。可能值得你用 Laravel 5.3 标签提出一个新问题以获得正确的社区支持。

以上是关于Laravel:使用 try...catch 和 DB::transaction()的主要内容,如果未能解决你的问题,请参考以下文章

测试 Laravel Controller 的 try catch

Guzzle Curl 错误未通过 try catch 语句捕获(Laravel)

解决 Laravel try catch 不工作的问题

Laravel - 如何在刀片视图中尝试/捕获?

php框架中,try,catch不能用的问题(转载)

try/catch 和 MFC TRY/CATCH 有啥区别?