Laravel Eloquent ORM 事务处理

131
"Eloquent ORM非常好,但我想知道是否有一种简便的方法来使用innoDB设置MySQL事务,就像PDO那样,或者我是否需要扩展ORM以使其成为可能?"
8个回答

216
你可以这样做:
DB::transaction(function() {
      //
});

闭包内的所有内容都在一个事务中执行。如果发生异常,它将自动回滚。


3
在闭包中,我可以调用一个类的查询吗?这样会起作用吗? - Rafael Soufraz
2
很遗憾,如果我创建不同模型的实例并将记录存储在它们自己相关的方法中,它对我不起作用。 - Volatil3
1
如果我在事务内捕获异常(用于生成错误消息等),我是否需要重新抛出异常以进行回滚? - alexw
8
很好的回答,但有几个问题需要注意:
  1. 你需要在模型文件的开头添加"use DB;"才能这样做。
  2. 与JS不同,除非你明确传递它们,否则你无法访问父级范围内的本地变量,你需要像这样添加"use"结构...DB::transaction(function() use ($user) { ...引用$user的内容... });
- Polsonby
2
根据文档所述:「DB 门面的事务方法控制查询构建器和 Eloquent ORM 的事务。」-> https://laravel.com/docs/8.x/database#database-transactions. - accexs
显示剩余4条评论

120

如果您不喜欢匿名函数:

try {
    DB::connection()->pdo->beginTransaction();
    // database queries here
    DB::connection()->pdo->commit();
} catch (\PDOException $e) {
    // Woopsy
    DB::connection()->pdo->rollBack();
}

更新: 对于 Laravel 4,pdo对象不再是公共的,因此:

try {
    DB::beginTransaction();
    // database queries here
    DB::commit();
} catch (\PDOException $e) {
    // Woopsy
    DB::rollBack();
}

16
你也可以使用快捷方法DB::beginTransaction()DB::commit()DB::rollback()。这会使代码更加简洁。 - Flori
2
请更新使用@Flori的建议,这样会更清晰。同时,将新答案向上移动将使您的答案更少混淆。我在回来第二个方法之前使用了第一个方法。 - frostymarvelous
对于较旧版本的Laravel,您可能需要使用以下代码:DB::connection()->getPdo()->beginTransaction(); - instead
我个人认为带回调函数的 DB::transaction 更加简洁,但缺点是如果您需要为不同的异常指定不同的处理程序,则必须返回到 try/catch 技术。 - OzzyTheGiant

46

如果要避免闭包,而又乐于使用门面(facades),以下方法可以保持代码整洁明了:

try {
    \DB::beginTransaction();

    $user = \Auth::user();
    $user->fill($request->all());
    $user->push();

    \DB::commit();

} catch (Throwable $e) {
    \DB::rollback();
}
如果任何语句失败,提交将永远无法执行,事务也不会处理。

1
如果有任何语句失败,后续语句将不会运行。您仍然需要显式地回滚事务。 - Jason
2
@Jason 我已经更新了答案。对于大多数(全部?)数据库引擎,当连接终止时,任何未提交的事务查询都不会被提交,我曾经犹豫过是否应该这样做。然而,我同意你的观点,最好明确说明。 - Chris

42

如果您想使用Eloquent,您也可以使用此代码。

这只是我的项目中的示例代码。

        /* 
         * Saving Question
         */
        $question = new Question;
        $questionCategory = new QuestionCategory;

        /*
         * Insert new record for question
         */
        $question->title = $title;
        $question->user_id = Auth::user()->user_id;
        $question->description = $description;
        $question->time_post = date('Y-m-d H:i:s');

        if(Input::has('expiredtime'))
            $question->expired_time = Input::get('expiredtime');

        $questionCategory->category_id = $category;
        $questionCategory->time_added = date('Y-m-d H:i:s');

        DB::transaction(function() use ($question, $questionCategory) {

            $question->save();

            /*
             * insert new record for question category
             */
            $questionCategory->question_id = $question->id;
            $questionCategory->save();
        });

在事务回调中,question->id 表达式返回零。 - Christos Papoulas
@user254153 当然。 - Aditya Kresna Permana
我们可以在闭包内部放置Mail::to()吗?或者只能放置与数据库相关的事务? - Blues Clues
1
@Jonjie 我相信你可以,但是在事务提交到数据库之后会更好。 - Aditya Kresna Permana
@AdityaKresnaPermana 是的,我尝试过了,在闭包内部它可以工作 :) - Blues Clues
显示剩余2条评论

22

我确定你不是在寻找一个结束方案,尝试这个更紧凑的解决方案。

 try{
    DB::beginTransaction();

    /*
     * Your DB code
     * */

    DB::commit();
}catch(\Exception $e){
    DB::rollback();
}

12

出于某种原因,在任何地方都很难找到这些信息,所以我决定在这里发布它,因为我的问题与Eloquent事务有关,而正好是更改以下内容。

阅读stackoverflow答案后,我意识到我的数据库表使用的是MyISAM而不是InnoDB。

对于Laravel(或任何其他地方)要使事务工作,需要将表设置为使用InnoDB

为什么?

引用MySQLTransactions and Atomic Operations文档 (此处):

MySQL Server (version 3.23-max and all versions 4.0 and above)支持使用InnoDB和BDB事务存储引擎的事务。InnoDB提供完整的ACID合规性。请参见第14章 存储引擎。有关InnoDB在处理事务错误方面与标准SQL不同之处的信息,请参见第14.2.11节 “InnoDB错误处理”。

MySQL服务器中的其他非事务性存储引擎(例如MyISAM)遵循称为“原子操作”的数据完整性不同范例。从事务角度看,MyISAM表实际上始终在autocommit = 1模式下运行。原子操作通常提供可比较的完整性和更高的性能。

由于MySQL服务器支持这两种范例,因此您可以决定使用原子操作的速度还是使用事务特性。这个选择可以基于每个表进行。


这对于DML是正确的,但对于DDL并不总是正确的。 - Yevgeniy Afanasyev

6
如果发生任何异常,事务将自动回滚。 Laravel基本事务格式
    try{
    DB::beginTransaction();

    /* 
    * SQL operation one 
    * SQL operation two
    ..................     
    ..................     
    * SQL operation n */


    DB::commit();
   /* Transaction successful. */
}catch(\Exception $e){       

    DB::rollback();
    /* Transaction failed. */
}

1

最好和清晰的方式:

DB::beginTransaction();

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接