我们都使用
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;
如果您需要通过代码手动“退出”交易(无论是通过异常还是只是检查错误状态),您不应该使用
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
}
请参阅交易文档。
如果您使用 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;
}
您可以将交易包装在 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){...}
获取它
我决定回答这个问题,因为我认为可以使用比复杂的 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 上测试
我正在使用 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
}
首先:在 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
}
在 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文档。
我建议你转到 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',
],
]
];