事务

在现代数据库中,事务可以做一些别的事情,例如保证在别人写数据的时候你没有访问数据的权限。但是,基本的思想是一样的——事务能保证无论发生什么事情,你使用的数据都是可感知的。他们保证不存在这样这样一种情况,钱从一个账户中拿走了,但没有进入另外一个账户中。

Yii2支持强大的带有保存点的事务机制。

一个经典的例子是,将钱从一个银行账户转移到另外一个银行账户中。为了做到这一点,首先你需要从源账户中取出钱,然后转移到目标账户中。这个操作必须全部成功。如果半道终止了,钱将会丢失。例如,我们有一个接收账户和一个发送账户。我们需要将钱从发送账户转移到接收账户中。假设我们有一个账户模型。

准备…

账户模型非常简单,只包含idbalance两个字段。

  1. 按照官方指南http://www.yiiframework.com/doc-2.0/guide-start-installation.html的描述,使用Composer包管理器创建一个新的应用。
  2. 创建一个migration,它会使用如下命令添加一个账户表:
  1. ./yii migrate/create create_account_table
  1. 同时,使用如下代码更新刚刚创建的migration:
  1. <?php
  2. use yii\db\Schema;
  3. use yii\db\Migration;
  4. class m150620_062034_create_account_table extends Migration
  5. {
  6. const TABLE_NAME = '{{%account}}';
  7. public function up()
  8. {
  9. $tableOptions = null;
  10. if ($this->db->driverName === 'mysql') {
  11. $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB';
  12. }
  13. $this->createTable(self::TABLE_NAME, [
  14. 'id' => Schema::TYPE_PK,
  15. 'balance' => ' NUMERIC(15,2) DEFAULT NULL',
  16. ], $tableOptions);
  17. }
  18. public function down()
  19. {
  20. $this->dropTable(self::TABLE_NAME);
  21. }
  22. }
  1. 然后使用如下命令安装migration:
  1. ./yii migrate up
  1. 使用Gii为账户表创建模型。
  2. 创建一个migration,他会添加一些测试Account模型到表中:
  1. ./yii migrate/create add_account_records
  1. 同时,使用如下代码更新刚刚创建的migration:
  1. <?php
  2. use yii\db\Migration;
  3. use app\models\Account;
  4. class m150620_063252_add_account_records extends Migration
  5. {
  6. public function up()
  7. {
  8. $accountFirst = new Account();
  9. $accountFirst->balance = 1110;
  10. $accountFirst->save();
  11. $accountSecond = new Account();
  12. $accountSecond->balance = 779;
  13. $accountSecond->save();
  14. $accountThird = new Account();
  15. $accountThird->balance = 568;
  16. $accountThird->save();
  17. return true;
  18. }
  19. public function down()
  20. {
  21. $this->truncateTable('{{%account}}');
  22. return false;
  23. }
  24. }

如何做…

  1. 添加如下规则到models/Account.php中的rules方法:
  1. public function rules()
  2. {
  3. return [
  4. //..
  5. [['balance'], 'number', 'min' => 0],
  6. //..
  7. ];
  8. }
  1. 假设balance只能是正的,不能是负值。
  2. TestController创建success和error动作:
  1. <?php
  2. namespace app\controllers;
  3. use app\models\Account;
  4. use Yii;
  5. use yii\db\Exception;
  6. use yii\helpers\Html;
  7. use yii\helpers\VarDumper;
  8. use yii\web\Controller;
  9. class TestController extends Controller
  10. {
  11. public function actionSuccess()
  12. {
  13. $transaction = Yii::$app->db->beginTransaction();
  14. try {
  15. $recipient = Account::findOne(1);
  16. $sender = Account::findOne(2);
  17. $transferAmount = 177;
  18. $recipient->balance += $transferAmount;
  19. $sender->balance -= $transferAmount;
  20. if ($sender->save() && $recipient->save()) {
  21. $transaction->commit();
  22. return $this->renderContent(
  23. Html::tag('h1', 'Money transfer was successfully')
  24. );
  25. } else {
  26. $transaction->rollBack();
  27. throw new Exception('Money transfer failed:' .
  28. VarDumper::dumpAsString($sender->getErrors()) .
  29. VarDumper::dumpAsString($recipient->getErrors())
  30. );
  31. }
  32. } catch ( Exception $e ) {
  33. $transaction->rollBack();
  34. throw $e;
  35. }
  36. }
  37. public function actionError()
  38. {
  39. $transaction = Yii::$app->db->beginTransaction();
  40. try {
  41. $recipient = Account::findOne(1);
  42. $sender = Account::findOne(3);
  43. $transferAmount = 1000;
  44. $recipient->balance += $transferAmount;
  45. $sender->balance -= $transferAmount;
  46. if ($sender->save() && $recipient->save()) {
  47. $transaction->commit();
  48. return $this->renderContent(
  49. Html::tag('h1', 'Money transfer was successfully')
  50. );
  51. } else {
  52. $transaction->rollBack();
  53. throw new Exception('Money transfer failed: ' .
  54. VarDumper::dumpAsString($sender->getErrors()) .
  55. VarDumper::dumpAsString($recipient->getErrors())
  56. );
  57. }
  58. } catch ( Exception $e ) {
  59. $transaction->rollBack();
  60. throw $e;
  61. }
  62. }
  63. }
  1. 运行test/success你会得到如下输出:

事务 - 图1

  1. 在这中情况下,如果发生了一些错误,事务机制不会更新接收者账户和发送者账户。
  2. 运行test/error你将会得到如下输出:

事务 - 图2

如果你记得,我们给Account模型添加了一条规则,所以我们的账户只能是正的。在这种情况下,事务将会回滚,它阻止了从发送者账户中取出钱,但没有钱到接收者账户中的情况。

参考

欲了解更多信息,参考: