欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Yii2中事务的使用实例代码详解

程序员文章站 2024-04-02 15:23:34
前言 一般我们做业务逻辑,都不会仅仅关联一个数据表,所以,会面临事务问题。 数据库事务(database transaction) ,是指作为单个逻辑工作单元执行的一系...

前言

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

数据库事务(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中。

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

模拟需求

接收参数:

名字

性别

签名

业务处理流程:

接收参数

由发号器得到用户的uid,发号器对应数据表增加一位数字

把名字、性别、签名和上一步的uid写入用户信息表

初始化用户余额表

回滚触发时机:

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

以上所述是小编给大家介绍的yii2中事务的使用实例代码详解,希望对大家有所帮助