首页 > 编程 > PHP > 正文

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

2020-03-22 20:18:15
字体:
来源:转载
供稿:网友
  • Yii2中事务的使用以及代码实例

    前言

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

    数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使html' target='_blank'>应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的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 yiidbException(); //手动抛出异常,再由下面捕获。            }            //提交            $tr->commit();        } catch (Exception $e) {            //回滚            $tr->rollBack();            echo  'rollback';        }    }

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

    save 1 | save 2 | save 3 | rollback

    分散的数据处理

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

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

    模拟需求

    接收参数:

    名字 性别 签名

    业务处理流程:

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

    回滚触发时机:

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

    实际代码

    //Controller    /**     * 测试事务-注册用户     */    public function actionReg()    {        //获取请求        $request = Yii::$app->request;        //设定返回格式        $response = Yii::$app->response;        $response->format = yiiwebResponse::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应用使用了同一个数据库连接,或者说是使用了单例。

    而在yiidbConnection中,又对事务对象进行了缓存:

    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 在整个应用的生命周期中,是有延续性的。 这是实现事务嵌套的关键和前提。

    总结

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

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

    PHP编程

    郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

  • 发表评论 共有条评论
    用户名: 密码:
    验证码: 匿名发表