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

问题描述 投票:0回答:8

我们都使用

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;
php laravel transactions
8个回答
302
投票

如果您需要通过代码手动“退出”交易(无论是通过异常还是只是检查错误状态),您不应该使用

DB::transaction()
,而应将代码包装在
DB::beginTransaction
DB::commit
/
DB::rollback()

DB::beginTransaction();

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

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

请参阅交易文档


42
投票

如果您使用 PHP7,请使用 catch 中的

Throwable
来捕获用户异常和致命错误。

例如:

DB::beginTransaction();

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

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

Exception
Throwable
:

DB::beginTransaction();

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

22
投票

您可以将交易包装在 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()
,它不会立即重定向,因为它返回到处理事务的变量。

结束交易

try {
    $result = DB::transaction(function () use ($request, $message) {
        // execute query 1
        // execute query 2
        // ..
    });          
    // redirect the page
    return redirect(route('account.article'));
} catch (\Exception $e) {
    return redirect()->back()->withErrors(['error' => $e->getMessage()]);
}

然后另一种方法是抛出布尔变量并处理外部事务函数的重定向,或者如果您需要检索事务失败的原因,您可以从

$e->getMessage()
内的
catch(Exception $e){...}

获取它

9
投票

我决定回答这个问题,因为我认为可以使用比复杂的 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()
    作为控制器的响应。
  • 如果您希望事务回滚,请确保
    throw
    出现异常(或者有一个嵌套函数自动为您抛出异常,就像 Eloquent 中的任何 SQL 异常一样)。
  • id
    updated_at
    created_at
    和任何其他字段在创建
    $user
    对象后可用(至少在此事务期间)。该事务将通过您拥有的任何创建逻辑运行。然而,当抛出
    SomeCustomException
    时,整个记录将被丢弃。尽管交易失败,但
    id
    的自动增量列确实会增加。

在 Laravel 5.8 上测试


4
投票

我正在使用 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
}

1
投票

首先:在 Laravel 中使用 PostgreSQL 数据库会让事情变得更加棘手。

如果在事务错误后不回滚,则每个进一步的查询都会抛出此错误在失败的sql事务中:错误:当前事务被中止,命令被忽略直到事务块结束。因此,如果您无法在回滚之前将原始错误消息保存在表中。

try {
    DB::beginTransaction(); //start transaction
    $user1 = User::find(1);
    $user1->update(['money' => 'not_a_number']); //bad update
}
catch(Exception $exception) {
    $user2 = User::find(2); // ko, "In failed sql transaction" error
    $user2->update(['field' => 'value']);
}

try {
    DB::beginTransaction(); //start transaction
    $user1 = User::find(1);
    $user1->update(['money' => 'not_a_number']); //bad update
}
catch(Exception $exception) {
    DB::rollBack();
    $user2 = User::find(2); // ok, go on
    $user2->update(['field' => 'value']);
}

第二:关注Eloquent模型属性系统。

Eloquent 模型在更新错误后保留更改的属性,因此如果我们想在 catch 块内更新该模型,我们需要丢弃错误的属性。这不是 dbtransaction 事件,因此回滚命令没有用。

try {
    DB::beginTransaction(); //start transaction
    $user1 = User::find(1);
    $user1->update(['money' => 'not_a_number']); //bad update
}
catch(Exception|Error $exception) {
    DB::rollBack();
    $user1->update(['success' => 'false']); // ko, bad update again
}

try {
    DB::beginTransaction(); //start transaction
    $user1 = User::find(1);
    $user1->update(['money' => 'not_a_number']); //bad update
}
catch(Exception|Error $exception) {
    DB::rollBack();
    $user1->discardChanges(); // remove attribute changes from model
    $user1->update(['success' => 'false']); // ok, go on
}

1
投票

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

try{
    DB::transaction(function() {
        // do anything
    });
}
catch(){
    // do anything
}

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

2023 年 11 月 18 日新更新。回复 Carlos 的评论

您不能在 catch 块中使用“DB::rollback”,因为它超出了“DB::transaction”块。如果你想在 catch 块中处理“DB::rollback”,最好使用手动事务:

DB::beginTransaction();

try {
    DB::update('update users set votes = 1');
    DB::delete('delete from posts');
    //if everything goes well
    DB::commit();
    
} catch (\Exception $e) {
    //if something goes wrong
    DB::rollback();
}   

您可以关注Laravel文档


0
投票

我建议你转到 config/database.php 并将引擎更改为 InnoDB。

与 MySQL 数据库的默认引擎 MyISAM 相比,MyISAM 支持事务。通过配置添加它将确保之后创建的所有表都将其作为默认值。

  <?php

use Illuminate\Support\Str;

return [

  // Other settings we don't care about.

    'connections' => [
      
        // Other connections we don't care about

        'mysql' => [
            // Other mysql configurations we don't care about
            'engine' => 'InnoDB',
        ],
    ]
];
© www.soinside.com 2019 - 2024. All rights reserved.