save 时怎么保留查询时的 where 条件(乐观锁实现)

请不要忘记看标题

怎么保留

// $model = X::query()->where('field1','a')->where('field2','b')->first();
// 更新一下 :
$model = X::query()->where('pk','value')->where('version','uuid')->first();
$model->field = 'abc';
$model->save();

得到的SQL是

UPDATE x set field = 'abc' where pk = 'id';
-- 期望sql👇
-- update x set field = 'abc' where pk = 'id' and version = 'uuid'

查询时的where条件丢失。Model::where()->update()又会不触发模型事件

《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
最佳答案
// 在你的模型中覆写这个方法
protected function performUpdate(Builder $query)
{
    $query->where('version', $this->version)
    return parent::performUpdate($query)
}

覆写这个方法可一做到,然后测试下,这个方法追踪后只找到了save()方法在调用,影响范围可控

4年前 评论
cevin (楼主) 4年前
cevin (楼主) 4年前
anniversary (作者) 4年前
cevin (楼主) 4年前
讨论数量: 17

你要查询条件干啥 更新时已经保留了主键筛选啊 主键确定了 其他什么条件都没有用啊

4年前 评论
cevin (楼主) 4年前

你想要得到什么样的sql

4年前 评论
cevin (楼主) 4年前
$model->where('version',$model->version)->update(['field3'=>'abc','version'=>$model->version+1])
4年前 评论
cevin (楼主) 4年前

没理解啊

4年前 评论
cevin (楼主) 4年前
lddtime 4年前
monanxiao (作者) 4年前
cevin (楼主) 4年前
// 在你的模型中覆写这个方法
protected function performUpdate(Builder $query)
{
    $query->where('version', $this->version)
    return parent::performUpdate($query)
}

覆写这个方法可一做到,然后测试下,这个方法追踪后只找到了save()方法在调用,影响范围可控

4年前 评论
cevin (楼主) 4年前
cevin (楼主) 4年前
anniversary (作者) 4年前
cevin (楼主) 4年前
小烦

....你指的模型事件是指啥?observer? 如果是监听者的话,update要触发observer 触发模型事件必须先get() 或 first() 比如:

//这个是会触发observer 
Tags::query()->where('tag', '签到任务')->first()->update(['title'=>'巴拉巴拉']);
//这个不会触发
Tags::query()->where('tag', '签到任务')->update(['title'=>'巴拉巴拉']);
4年前 评论

重写一下 model 中的 save 方法,应该能达到你想要的效果。: )


public function save(array $options = [],$otherWhere=[])
    {
        $query = $this->newModelQuery();

       $otherWhere && $query->where($otherWhere)

        // If the "saving" event returns false we'll bail out of the save and return
        // false, indicating that the save failed. This provides a chance for any
        // listeners to cancel save operations if validations fail or whatever.
        if ($this->fireModelEvent('saving') === false) {
            return false;
        }

        // If the model already exists in the database we can just update our record
        // that is already in this database using the current IDs in this "where"
        // clause to only update this model. Otherwise, we'll just insert them.
        if ($this->exists) {
            $saved = $this->isDirty() ?
                        $this->performUpdate($query) : true;
        }

        // If the model is brand new, we'll insert it into our database and set the
        // ID attribute on the model to the value of the newly inserted row's ID
        // which is typically an auto-increment value managed by the database.
        else {
            $saved = $this->performInsert($query);

            if (! $this->getConnectionName() &&
                $connection = $query->getConnection()) {
                $this->setConnection($connection->getName());
            }
        }

        // If the model is successfully saved, we need to do a few more things once
        // that is done. We will call the "saved" method here to run any actions
        // we need to happen after a model gets successfully saved right here.
        if ($saved) {
            $this->finishSave($options);
        }

        return $saved;
    }
4年前 评论
cevin (楼主) 4年前
superSnail (作者) 4年前

$this->getAttributeValue($key)我用了你这个方法

<?php
namespace App\Traits\Models;
use Illuminate\Database\Eloquent\Builder;

trait OptimisticLock
{
    protected function performUpdate(Builder $builder)
    {


        if (method_exists($this, 'optimistic'))
        {


            foreach ($this->optimistic() as $key)
            {

                $builder->where($key, $this->getAttributeValue($key)); // $this->getAttributeValue($key) 这里拿到的版本号是加1过的,所以不匹配了
            }
        }

        parent::performUpdate($builder);
    }
}

但是

public function charges(User $user, Request $request) {

        if($request->isMethod('get')) {
            return view('admin.users.charges', compact('user'));
        } else {
            DB::beginTransaction();
            try {

                $user->new_version = $user->new_version + 1; // 我希望在这里每次保存时增加版本号
                $user->amount = $user->amount + $request->charge;
                $user->save();

                $user->ledger()->create(['enum' => 'charges', 'charges' => $request->charge, 'description' => $request->description, 'note' => $request->note]);

                DB::commit();
            } catch (\Exception $e) {
                DB::rollBack();
                throw $e;
            }

            return $this->success('充值成功', route('admin.users.index'));
        }
    }

每次save的时候,我都会写入新的version,那么会有一个问题,会导致model中的performUpdate where 无法执行了,因为版本号已经加1了,你有啥好思路没

2年前 评论
<?php
namespace App\Traits\Models;
use Illuminate\Database\Eloquent\Builder;

trait OptimisticLock
{
    protected function performUpdate(Builder $builder)
    {
        if (method_exists($this, 'optimistic'))
        {
            foreach ($this->optimistic() as $key)
            {
                $builder->where($key, $this->getOriginal($key));
            }
        }

        parent::performUpdate($builder);
    }
}
2年前 评论
anniversary 2年前
深蓝色 (作者) 2年前

改成这样可以,但是save的时候也没有办法知道是否成功了,

$user->new_version = $user->new_version + 1; $user->amount = $user->amount + $request->charge; $rs = $user->save(); // 这里也无法判断是否更新成功,还是有缺陷,返回值永远是$null,否是更新了几条记录也是一样

2年前 评论

这样还是有一个问题,save只能知道是否执行成功,但不知道是否真的update

另外,因为是在Model里面获得原始值的版本号,如果程序在前面已经修改过像amount的话,一样也会出错

2年前 评论

这个有谁有更好的办法没

2年前 评论

@深蓝色

这样还是有一个问题,save 只能知道是否执行成功,但不知道是否真的 update

没有异常就是成功,通过 wasChanged() 判断当前记录(的某个字段)是否修改,类似的方法还有 isDirty isClean,详见 检查属性变更

因为是在 Model 里面获得原始值的版本号,如果程序在前面已经修改过像 amount 的话,一样也会出错

这里的出错是指什么

另外,我自己怎么从没碰到过这样的场景,
我之前在上面的回复是「当你需要 save 的时候就已经不需要额外条件了 」,
没太理解这么多回复到底解决的什么问题。
2年前 评论

问题解决了,应该就用这个方案,没问题了

2年前 评论

乐观锁啊,防止数据被其它线程给更改,导致参与计算进去的数据不对

2年前 评论

@lddtime 很遗憾,不行,这个只能判断ORM对像是否更新,不能真实判断是否SQL更新

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!