【PHP】Yii2中事務的使用以及程式碼例項

小雨雨hi發表於2016-09-07

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 yiidbException(); //手動丟擲異常,再由下面捕獲。
            }
            //提交
            $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 = 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 在整個應用的生命週期中,是有延續性的。 這是實現事務巢狀的關鍵和前提。

總結

技術水平的增長來自踩坑,踩坑來自業務增長。

有時間聊聊職業規劃和發展。 : )

參考資料


相關文章