前言
一般我们做业务逻辑,都不会仅仅关联一个数据表,所以,会面临事务问题。
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的一个逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
准备
数据库引擎为innodb
本文使用的yii版本为2.0.5,只要是2.0以上就没有问题
运行环境为PHP7.0.0,Mysql5.6
Yii中的事务
处理异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/**
* 测试事务
*/
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" ;
}
}
|
运行结果
1
|
save 1 | save 2 | save 3 | rollback
|
注意,因为最后的数据没有插入成功,触发了事务的回滚,所以数据表没有新增数据产生。
触发事务回滚的原因是代码出现了异常(Exception)。
处理数据失败
一般来讲,我们的运行中的代码是不会出现这种明显的异常,这种异常会在开发测试过程中消灭掉。
而真正造成数据需要回滚的是我们的某个业务出现问题,导致没有写入部分的数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/**
* 测试事务
*/
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" ;
}
}
|
运行结果如下,数据库插入了三条数据。
1
|
save 1 | save 2 | save 3 | save fail
|
也就是说,如果因为业务逻辑导致某个数据表没有写入数据,也没有出现对应的回滚。
改进方案如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/**
* 测试事务
*/
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" ;
}
}
|
运行结果如下,数据库没有插入新数据,事务被回滚。
1
|
save 1 | save 2 | save 3 | rollback
|
分散的数据处理
由于实际项目的复杂程度,导致我们的数据库操作分散在不同的Model中。
所以,实际项目的代码不会是上面的样子。
模拟需求
接收参数:
名字
性别
签名
业务处理流程:
接收参数
由发号器得到用户的uid,发号器对应数据表增加一位数字
把名字、性别、签名和上一步的uid写入用户信息表
初始化用户余额表
回滚触发时机:
初始化余额表没有传入uid导出没有写入数据
实际代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
//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;
}
|
正常的结果如下
1
|
{ "id" :124, "user_id" :1473179883, "price" : "0" , "update_time" :1473179883}
|
如果把初始化用户余额部分的user_id没有传递成功,返回的结果如下
1
|
"没有初始化用户余额"
|
我们可以针对具体情况定位到错误所在位置,及时修改。
事务(Transaction)
从上面的实际代码可以看出,创建了事务,只要在范围内,就算是引入的别的Model也能把异常NG返回,完成回滚操作。
一般情况下,整个Yii应用使用了同一个数据库连接,或者说是使用了单例。
而在yii\db\Connection中,又对事务对象进行了缓存:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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中事务的使用实例代码详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!