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

本文详细介绍了在Yii框架中如何使用数据库事务处理数据操作,包括开始事务、提交、回滚以及在遇到异常时如何确保数据一致性。通过示例代码展示了事务在业务逻辑中的应用,如注册用户时的事务处理,确保用户信息和余额表数据的完整性和一致性。同时,讨论了在多表操作和异常处理中事务的重要性,并提供了具体的解决方案。
摘要由CSDN通过智能技术生成

前言

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

数据库事务(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
  • 1

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

触发事务回滚的原因是代码出现了异常(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
  • 1

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

改进方案如下


/**

* 测试事务

*/

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
  • 1

分散的数据处理

由于实际项目的复杂程度,导致我们的数据库操作分散在不同的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}
  • 1

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

"没有初始化用户余额"
  • 1

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值