【PHP】Yii2中事务的使用以及代码实例

简介: Yii2中事务的使用以及代码实例前言一般我们做业务逻辑,都不会仅仅关联一个数据表,所以,会面临事务问题。 数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。

Yii2中事务的使用以及代码实例

前言

一般我们做业务逻辑,都不会仅仅关联一个数据表,所以,会面临事务问题。

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的一个逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。

准备

  • 数据库引擎为innodb
  • 本文使用的yii版本为2.0.5,只要是2.0以上就没有问题
  • 运行环境为PHP7.0.0,Mysql5.6

Yii中的事务

处理异常


    /**
     * 测试事务
     */
    public function actionTest(){
        //创建事务
        $tr = Yii::$app->db->beginTransaction();
        try {
            for($i=1;$i<=3;$i++){
                $test = new Areas();
                $test->name = 'name'.$i;
                $test->sort=1;
                if($test->save()){
                    echo "save $i | ";
                }

            }
            $test = new Areas();
            $test->name = 'ab'.$i;
            $test->sorta=1; //写入不存在的字段
            if(!$test->save()){
                "save fail"; //如果没有写入就输出
            }
            //提交
            $tr->commit();
        } catch (Exception $e) {
            //回滚
            $tr->rollBack();
            echo  "rollback";
        }
    }

运行结果

save 1 | save 2 | save 3 | rollback

注意,因为最后的数据没有插入成功,触发了事务的回滚,所以数据表没有新增数据产生。

触发事务回滚的原因是代码出现了异常(Exception)。

处理数据失败

一般来讲,我们的运行中的代码是不会出现这种明显的异常,这种异常会在开发测试过程中消灭掉。

而真正造成数据需要回滚的是我们的某个业务出现问题,导致没有写入部分的数据。


    /**
     * 测试事务
     */
    public function actionTest(){
        //创建事务
        $tr = Yii::$app->db->beginTransaction();
        try {
            for($i=1;$i<=3;$i++){
                $test = new Areas();
                $test->name = 'name'.$i;
                $test->sort=1;
                if($test->save()){
                    echo "save $i | ";
                }
            }
            $test = new Areas();
            $test->name = null; //数据库设计name不能为空,人为造成写入失败。
            $test->sort=1; //写入不存在的字段
            if(!$test->save()){
               echo  "save fail"; //如果没有写入就输出
            }
            //提交
            $tr->commit();
        } catch (Exception $e) {
            //回滚
            $tr->rollBack();
            echo  "rollback";
        }
    }

运行结果如下,数据库插入了三条数据。

save 1 | save 2 | save 3 | save fail

也就是说,如果因为业务逻辑导致某个数据表没有写入数据,也没有出现对应的回滚。

改进方案如下

    /**
     * 测试事务
     */
    public function actionTest(){
        //创建事务
        $tr = Yii::$app->db->beginTransaction();
        try {
            for($i=1;$i<=3;$i++){
                $test = new Areas();
                $test->name = 'name'.$i;
                $test->sort=1;
                if($test->save()){
                    echo "save $i | ";
                }
            }
            $test = new Areas();
            $test->name = null; //数据库设计name不能为空,人为造成写入失败。
            $test->sort=1; //写入不存在的字段
            if(!$test->save()){
               throw new \yii\db\Exception(); //手动抛出异常,再由下面捕获。
            }
            //提交
            $tr->commit();
        } catch (Exception $e) {
            //回滚
            $tr->rollBack();
            echo  "rollback";
        }
    }

运行结果如下,数据库没有插入新数据,事务被回滚。

save 1 | save 2 | save 3 | rollback

分散的数据处理

由于实际项目的复杂程度,导致我们的数据库操作分散在不同的Model中。

所以,实际项目的代码不会是上面的样子。

模拟需求

接收参数:

  • 名字
  • 性别
  • 签名

业务处理流程:

  1. 接收参数
  2. 由发号器得到用户的uid,发号器对应数据表增加一位数字
  3. 把名字、性别、签名和上一步的uid写入用户信息表
  4. 初始化用户余额表

回滚触发时机:

  • 初始化余额表没有传入uid导出没有写入数据

实际代码


//Controller


    /**
     * 测试事务-注册用户
     */
    public function actionReg()
    {
        //获取请求
        $request = Yii::$app->request;
        //设定返回格式
        $response = Yii::$app->response;
        $response->format = \yii\web\Response::FORMAT_JSON; //返回json

        //测试代码,去掉验证身份步骤

        $name = $request->get("name");
        $gender = $request->get("gender");
        $sign = $request->get("sign");

        //测试代码,省略参数校验步骤

        $tr = Yii::$app->db->beginTransaction();
        try {
            //得到uid
            $uid = App::getSeNo();
            UserProfile::add($uid, $name, $gender, 1, $sign);
            $user_balance =    UserBalance::initUserBalance($uid);
            $tr->commit(); //提交数据
        } catch (Exception $e) {
            //回滚
            $tr->rollBack();
            return $e->getMessage(); //返回自定义异常信息
        }
        return $user_balance;
    }

//UserProfile

    /**
     * 添加用户信息
     * @param $user_id
     * @param $nikename
     * @param $gender
     * @param $user_type
     * @param string $intro
     * @return UserProfile
     * @throws \Exception
     */
    public static function add($user_id, $nikename, $gender,$user_type,$intro="") {
        $model = new UserProfile();
        $model->gender = $gender;
        $model->nikename = $nikename;
        $model->user_id = $user_id;
        $model->user_type=$user_type;
        $model->intro=$intro;
        $model->update_time = time();
        $insert =$model->insert();
        if(!$insert){
            throw new Exception("没有写入用户资料");
        }
        return $model;
    }

//UserBalance

    /**
     * 初始化用户的可提现余额
     * @param $user_id
     */
    public static function initUserBalance($user_id){
        $info=self::find()->where(['user_id'=>$user_id])->one();

        if(!$info ){
            $model=new UserBalance();
            $model->user_id = $user_id;
            $model->price= "0";
            $model->update_time=time();
            $insert = $model->insert();
            if(!$insert){
                throw new Exception("没有初始化用户余额");
            }
            $info=$model;
        }
        return $info->attributes;
    }

正常的结果如下

{"id":124,"user_id":1473179883,"price":"0","update_time":1473179883}

如果把初始化用户余额部分的user_id没有传递成功,返回的结果如下

"没有初始化用户余额"

我们可以针对具体情况定位到错误所在位置,及时修改。

事务(Transaction)

从上面的实际代码可以看出,创建了事务,只要在范围内,就算是引入的别的Model也能把异常NG返回,完成回滚操作。

一般情况下,整个Yii应用使用了同一个数据库连接,或者说是使用了单例。

而在yii\db\Connection中,又对事务对象进行了缓存:

class Connection extends Component
{
    // 保存当前连接的有效Transaction对象
    private $_transaction;

    // 已经缓存有事务对象,且事务对象有效,则返回该事务对象
    // 否则返回null
    public function getTransaction()
    {
        return $this->_transaction && $this->_transaction->getIsActive() ? $this->_transaction : null;
    }

    // 看看启用事务时,是如何使用事务对象的
    public function beginTransaction($isolationLevel = null)
    {
        $this->open();
        // 缓存的事务对象有效,则使用缓存中的事务对象
        // 否则创建一个新的事务对象
        if (($transaction = $this->getTransaction()) === null) {
            $transaction = $this->_transaction = new Transaction(['db' => $this]);
        }
        $transaction->begin($isolationLevel);
        return $transaction;
    }
}

因此,可以认为整个Yii应用,使用了同一个 Transaction 对象,也就是说, Transaction::_level 在整个应用的生命周期中,是有延续性的。 这是实现事务嵌套的关键和前提。

总结

技术水平的增长来自踩坑,踩坑来自业务增长。

有时间聊聊职业规划和发展。 : )

参考资料

目录
相关文章
|
2天前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提高代码的可维护性与扩展性
在本文中,我们将深入探讨PHP编程语言中设计模式的重要性,以及如何通过应用这些模式来提高代码的可维护性和扩展性。设计模式是一套被反复使用、经过分类编目的代码设计经验的总结。它们代表了最佳的实践,能帮助开发者编写出更加健壮、灵活和可复用的代码。本文将介绍几种常见的设计模式,并通过PHP代码示例展示其应用。
|
4天前
|
缓存 NoSQL PHP
使用PHP-redis实现键空间通知监听key失效事件的技术与代码示例
通过上述方法,你可以有效地在PHP中使用Redis来监听键空间通知,特别是针对键失效事件。这可以帮助你更好地管理缓存策略,及时响应键的变化。
18 3
|
5天前
|
PHP 开发者
PHP中的异常处理:提升代码的健壮性与可维护性
【9月更文挑战第12天】在编程的世界里,错误和异常是不可避免的。它们像是旅途中的绊脚石,让我们的步伐变得蹒跚。然而,正是这些挑战,塑造了我们解决问题的能力。本文将引导你了解如何用PHP的异常处理机制来捕捉这些“绊脚石”,并将其转化为成长的阶梯。我们将一起探索如何通过异常处理来增强代码的健壮性和可维护性,让你的程序在面对未知和意外时,依然能够优雅地舞蹈。
|
12天前
|
PHP
PHP中的异常处理:提升代码的健壮性
【9月更文挑战第5天】在PHP编程中,异常处理是确保应用稳定性和可靠性的关键机制。本文将深入探讨如何使用PHP的异常处理来捕捉错误、清理资源并优雅地恢复程序流程,同时通过实际代码示例展示如何实现这一过程。
|
12天前
|
IDE PHP 开发工具
PHP中的异常处理:提升代码的健壮性
【9月更文挑战第5天】在PHP编程中,异常处理是一种强大的错误管理机制,允许开发者编写更加清晰、可维护的代码。本文将深入探讨PHP的异常处理,从基础概念到高级应用,旨在指导读者如何通过异常处理来提高代码质量,避免常见的陷阱,并实现更优雅的错误处理策略。
|
20天前
|
PHP
PHP中的面向对象编程:构建更高效的代码
【8月更文挑战第29天】在PHP的世界中,面向对象编程(OOP)是提升代码可维护性、可扩展性和复用性的利器。本文将通过浅显易懂的方式,带领读者探索PHP中OOP的核心概念,并通过实际代码示例揭示如何利用这些概念来编写更加高效和优雅的代码。无论你是PHP新手还是希望深化理解的老手,这篇文章都将为你打开一扇新窗,让你看到编码的艺术与哲学。
|
22天前
|
数据处理 PHP 开发者
PHP中的数组操作技巧:提高代码效率与可读性
【8月更文挑战第27天】在PHP开发中,数组是数据处理的核心。掌握数组的操作不仅能提升代码的执行效率,还能增强代码的可读性。本文将通过实际的编码示例,展示如何在PHP中高效地使用数组,包括数组的创建、遍历、排序、查找和过滤等常用操作,以及一些高级技巧,如使用匿名函数和数组的解构赋值。无论你是新手还是有经验的开发者,这些技巧都将对你的PHP编程之旅大有裨益。
|
18天前
|
PHP UED 开发者
PHP中的异常处理:提升代码的健壮性
【8月更文挑战第31天】在编程的世界里,异常就像是不请自来的客人,总是在不经意间打乱我们的计划。PHP作为一门流行的后端开发语言,提供了一套异常处理机制来帮助我们优雅地应对这些不速之客。本文将带你了解如何在PHP中捕获和处理异常,确保你的应用程序即使在面对意外情况时也能稳健运行。我们将通过实际的代码示例,展示如何实施有效的异常处理策略,以及如何利用自定义异常类来增强代码的可读性和可维护性。准备好迎接那些不请自来的“客人”了吗?让我们一起深入PHP的异常世界,学习如何将这些意外转化为我们的优势。
|
21天前
|
PHP 开发者
探索PHP中的异常处理:提升代码的健壮性
【8月更文挑战第28天】在编程的世界里,错误和异常是避免不了的现实。它们像是路上的坑洼,让代码之旅颠簸不已。PHP作为流行的后端开发语言之一,提供了异常处理机制,帮助开发者优雅地应对这些“坑洼”。本文将带你深入了解PHP中的异常处理,从基础概念到实际应用,让你的代码在遇到问题时能够稳稳当当,而不是一头栽倒。我们将一起学习如何捕获异常、如何处理异常,并且通过一些实用的代码示例,让你的PHP技能更上一层楼。
|
21天前
|
存储 中间件 PHP
Python编程入门:从零到一的代码实践深入理解 PHP 中的中间件模式
【8月更文挑战第28天】本文旨在通过浅显易懂的方式,向初学者介绍Python编程的基础知识,并结合具体代码示例,带领读者一步步实现从零基础到能够独立编写简单程序的转变。文章将围绕Python语言的核心概念进行讲解,并通过实例展示如何应用这些概念解决实际问题。无论你是编程新手还是希望扩展技能的专业人士,这篇文章都将为你打开编程世界的大门。 【8月更文挑战第28天】在PHP的世界中,设计模式是构建可维护和可扩展软件的重要工具。本文将通过浅显易懂的语言和生动的比喻,带领读者深入理解中间件模式如何在PHP应用中发挥魔力,实现请求处理的高效管理。我们将一步步揭开中间件的神秘面纱,从它的定义、工作原理到