这可能是并发用户在同一应用程序中工作时最常见的问题之一。
用户A从数据库中获取一个模型,该模型在屏幕上显示给用户。他执行一些操作,并更新该对象的值。
在特定时间保存模型。用户A几乎不知道在他获取模型并保存修改后的模型之间,用户B已经获取了相同模型,并将其更改提交给数据库。结果:用户B的更改丢失。
我已经解决了很多相同的问题,通常这样做是这样的:
1 /向模型$version
添加一个属性
2 /创建新模型后,我们设置$version = 1
3 /在获取模型时,显然也会获取version属性。
4 /保存修改的模型时,我只保存... where id = $model->id AND WHERE version = $model->version
,同时更新$version = $version + 1
当该更新抛出数据库“没有记录更新”时,我仅使用id搜索模型;如果可以获取,我知道模型已经存在,但是版本不再匹配。然后,我向用户提出一个例外(说“某人或某物已经修改了模型”)
我相信这是确保始终更新对象的最新版本的唯一正确方法。显然,您也可以使用updated_at属性来比较您是否仍具有相同版本的对象,但是理论上其他用户可能仍对其进行了更新。
现在,我正在为如何在Laravel中设置此逻辑而苦苦挣扎。我想创建一个可以在模型上使用的特征,该特征基本上可以完成我在上面描述的工作,但是我在为应该做的事情而苦苦挣扎。
我知道我可以覆盖protected function setKeysForSaveQuery($query)
以使其更新where id = $this->id and version = $this->version
,但是当我这样做时,laravel不更新任何内容时,我没有得到允许我确定更新的异常。因版本不匹配而失败...
我希望避免在将保存内容封装到事务中的地方编写代码,并且每次保存模型时都必须验证版本;寻找一种通过包含特征将它们放入我的基本模型中的方法,以便它自动地起作用。
任何输入将不胜感激。
-添加示例:
想象一下:
$modelcopy1 = App\Model::find(1); // version = 1
$modelcopy2 = App\Model::find(1); // version = 1
$modelcopy1->save(); // this should update version in database to 2
// also $modelcopy1->version should now hold 2
$modelcopy2->save(); // this should now throw an exception
// because copy 1 still references version 1
// database version is already at 2
因此,基本上,我需要->save()
中的逻辑来验证version属性的模型值与当前数据库版本匹配,并且需要同步,以便在完成更新时,model version属性为已更新为最新版本。
保存应如下图所示:
UPDATE Model
SET version = version + 1, attr1 = :attr1, attr2 = :attr2, ...
WHERE version = $model->version
AND id = $model->id
如果UPDATE更新成功->确保$ model-> version已更新为新版本值
如果UPDATE更新失败,则从* WHERE id = $ model-> id的模型中进行选择
如果找到模型,则THROW ERROR'模型已被其他人更新'ELSE THROW ERROR'模型不存在'
我希望这是有道理的。我认为,这种防止锁定的机制应该成为任何“成人”框架的一部分,但是将其绑定到Laravel框架中似乎很难...
答案 0 :(得分:0)
因此,您想编写一个特征来执行检查,以查看自收到当前模型以来是否已对模型进行了更新,并抛出可挂钩的内容以通知用户?我将研究制作所有模型都可以扩展的基础模型,并在该基础模型中使用模型事件。如果仅让所有模型扩展基本模型,则甚至不需要特征。参见下面的示例模型事件方法:
protected static function boot(){
parent::boot();
static::saving(function($thisModel){
$changes = Model::where('version', $thisModel->version)
->where('id', $thisModel->id)->first()
/** if changes->version matches this one, good, if not, set some
* property on the model that can be checked.
*/
}
}
在保存模型时会发生 ,如果您在方法中的任何时候return false;
,保存都会失败,并且不会保存模型。
如果仍然要使用特征,请确保boot方法遵循以下格式:bootTraitName(),否则它将不会在实现类的启动时启动。这是一个棘手的问题,有很多不同的角度,但我希望至少给您一些考虑的问题。
编辑:我从没有在基本模型上使用过模型事件,所以不确定parent::boot();
是否必要。可能要测试。