管理平台有一个设置的功能,有些时候在不同部门的同事可能因为一件事情在同一时间编辑这个设置功能,然而结果肯定是一个覆盖另外一个,如果不是仔细的分析具体原因,根本不知道是为什么发生如此的问题。

而乐观锁解决了此问题。

原理

数据库字段中有一个存储版本的字段,每编辑一次就+1,这样后提交的人的版本就会落后与最新的版本,从而达到防治覆盖错误的效果。

案例

wiki

  1. CREATE TABLE `wiki` (
  2. `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  3. `title` varchar(30) NOT NULL COMMENT '标题',
  4. `content` varchar(255) NOT NULL COMMENT '内容',
  5. `version` bigint(20) NOT NULL DEFAULT '0' COMMENT '乐观锁版本标识',
  6. PRIMARY KEY (`id`)
  7. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

model

  1. <?php
  2. namespace common\models;
  3. use yii\behaviors\OptimisticLockBehavior;
  4. /**
  5. * This is the model class for table "{{%wiki}}".
  6. *
  7. * @property int $id
  8. * @property string $title 标题
  9. * @property string $content 内容
  10. * @property int $version 乐观锁版本标识
  11. */
  12. class Wiki extends \yii\db\ActiveRecord
  13. {
  14. public function behaviors ()
  15. {
  16. return [
  17. // 这个行为类在 2.0.16 版本加入
  18. OptimisticLockBehavior::className()
  19. ];
  20. }
  21. /**
  22. * 重写 optimisticLock,暴露版本字段
  23. *
  24. * {@inheritdoc}
  25. * @see \yii\db\BaseActiveRecord::optimisticLock()
  26. */
  27. public function optimisticLock ()
  28. {
  29. return 'version';
  30. }
  31. /**
  32. *
  33. * {@inheritdoc}
  34. */
  35. public static function tableName ()
  36. {
  37. return '{{%wiki}}';
  38. }
  39. /**
  40. *
  41. * {@inheritdoc}
  42. */
  43. public function rules ()
  44. {
  45. return [
  46. [
  47. [
  48. 'title',
  49. 'content'
  50. ],
  51. 'required'
  52. ],
  53. [
  54. [
  55. 'version'
  56. ],
  57. 'integer'
  58. ],
  59. [
  60. [
  61. 'title'
  62. ],
  63. 'string',
  64. 'max' => 30
  65. ],
  66. [
  67. [
  68. 'content'
  69. ],
  70. 'string',
  71. 'max' => 255
  72. ]
  73. ];
  74. }
  75. /**
  76. *
  77. * {@inheritdoc}
  78. */
  79. public function attributeLabels ()
  80. {
  81. return [
  82. 'id' => 'ID',
  83. 'title' => '标题',
  84. 'content' => '内容',
  85. 'version' => '乐观锁版本标识'
  86. ];
  87. }
  88. }

controller

  1. <?php
  2. namespace backend\controllers;
  3. use common\models\Wiki;
  4. use yii\base\UserException;
  5. use yii\db\StaleObjectException;
  6. use yii\web\Controller;
  7. class WikiController extends Controller
  8. {
  9. public function actionEdit ($id)
  10. {
  11. $model = Wiki::findOne($id);
  12. if(empty($model))
  13. {
  14. throw new UserException('未找到wiki信息');
  15. }
  16. // 2.0.36 版本之后才可以使用 $this->request
  17. if($this->request->isPost)
  18. {
  19. if($model->load($this->request->post()) && $model->validate())
  20. {
  21. try
  22. {
  23. $res = $model->save();
  24. }
  25. catch(StaleObjectException $e)
  26. {
  27. throw new UserException('有他人已经编辑过了,你的文档过于陈旧,请刷新后重新编辑');
  28. }
  29. // 保存成功
  30. if(false !== $res)
  31. {
  32. return json_encode([
  33. 'code' => 0,
  34. 'message' => '保存成功'
  35. ]);
  36. }
  37. }
  38. throw new UserException('数据验证失败:' . json_encode($model->errors));
  39. }
  40. return $this->render('edit', [
  41. 'model' => $model
  42. ]);
  43. }
  44. }

view

  1. <html lang="en">
  2. <head>
  3. <meta charset="utf-8">
  4. <title><?= $model->title?></title>
  5. </head>
  6. <body>
  7. <form name="/wiki/edit?id=<?=$model->id?>" method="post">
  8. <input name="Wiki[title]" value="<?=$model->title?>" />
  9. <br>
  10. <textarea name="Wiki[content]">
  11. <?=$model->content?>
  12. </textarea>
  13. <br>
  14. <input type="hidden" name="_csrf-backend" value="<?= Yii::$app->request->csrfToken ?>" />
  15. <input type="hidden" name="Wiki[version]" value="<?=$model->version?>" />
  16. <button type="submit">提交</button>
  17. </form>
  18. </body>
  19. </html>

效果

后编辑提交的会得到错误提示。

image.png