一般我们做业务逻辑,都不会仅仅关联一个数据表,所以,会面临事务问题。
数据库事务 (Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的 ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的一个逻辑工作单位,由 DBMS 中的事务管理子系统负责事务的处理。
- /**
- * 测试事务
- */
- 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 没有传递成功,返回的结果如下
- "没有初始化用户余额"
我们可以针对具体情况定位到错误所在位置,及时修改。
从上面的实际代码可以看出,创建了事务,只要在范围内,就算是引入的别的 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 在整个应用的生命周期中,是有延续性的。 这是实现事务嵌套的关键和前提。
技术水平的增长来自踩坑,踩坑来自业务增长。
有时间聊聊职业规划和发展。 : )
来源: http://lib.csdn.net/article/php/42838