PHP红豆存储bean(如果不存在)
我有点困惑。我在我的直邮服务中积极地使用PHP RedBean作为ORM,我遇到了一个奇怪的情况——我有一个具有唯一键约束(即订户id、传递id)的表和两个将数据写入该表的脚本。 有正在插入或更新表的源代码:PHP红豆存储bean(如果不存在),php,mysql,database,orm,redbean,Php,Mysql,Database,Orm,Redbean,我有点困惑。我在我的直邮服务中积极地使用PHP RedBean作为ORM,我遇到了一个奇怪的情况——我有一个具有唯一键约束(即订户id、传递id)的表和两个将数据写入该表的脚本。 有正在插入或更新表的源代码: public static function addOpenPrecedent($nSubscriberId, $nDeliveryId) { $oOpenStatBean = \R::findOrDispense('open_stat', 'delivery_id = :did
public static function addOpenPrecedent($nSubscriberId, $nDeliveryId)
{
$oOpenStatBean = \R::findOrDispense('open_stat', 'delivery_id = :did AND subscriber_id = :sid', array(':did' => $nDeliveryId, ':sid' => $nSubscriberId));
$oOpenStatBean = array_values($oOpenStatBean);
if (1 !== count($oOpenStatBean)) {
throw new ModelOpenStatException(
"Ошибка при обновлении статистики открытий: пара (delivery_id,
subscriber_id) не является уникальной: ($nDeliveryId, $nSubscriberId).");
}
$oOpenStatBean = $oOpenStatBean[0];
if (!empty($oOpenStatBean->last_add_dt)) {
$oOpenStatBean->precedent++;
} else {
$oOpenStatBean->delivery_id = $nDeliveryId;
$oOpenStatBean->subscriber_id = $nSubscriberId;
}
$oOpenStatBean->last_add_dt = time('Y-m-d H:i:s');
\R::store($oOpenStatBean);
}
它从两个脚本中同时调用。我周期性地对这个表的唯一约束有问题,因为竞争条件会发生。我知道SQL“重复密钥更新时插入”功能。但是,我怎样才能纯粹使用ORM获得相同的结果呢?我知道如果Redbean不发布
INSERT ON DUPLICATE KEY UPDATE
正如上面评论中引用的讨论所表明的,Redbean的开发人员认为upsert是一个业务逻辑问题,它会污染ORM的界面。也就是说,如果一个人用一个自定义的查询编写器或插件来扩展Redbean,那么很可能是可以实现的。我没有尝试过这种方法,因为下面的方法很容易实现这种行为,而不会弄乱ORM的内部和插件,但是,它确实需要使用事务和模型以及一些额外的查询
基本上,在调用R::store()之前,使用R::transaction()或R::begin()启动事务。然后在“FUSE”d模型中,使用“update”FUSE方法运行一个查询,该查询检查重复并检索现有id,同时锁定必要的行(即选择进行更新)。若并没有返回id,那个么就好了,让常规模型验证(或缺少验证)像往常一样继续并返回。如果找到id,只需将$this->bean->id设置为返回值,Redbean将更新而不是插入。因此,对于这样的模型:
class Model_OpenStat extends RedBean_SimpleModel{
function update(){
$sql = 'SELECT * FROM `open_stat` WHERE `delivery_id`=? AND 'subscriber_id'=? LIMIT 1 FOR UPDATE';
$args = array( $this->bean->deliver_id, $this->bean->subscriber_id );
$dupRow = R::getRow( $sql, $args );
if( is_array( $dupRow ) && isset( $dupRow['id'] ) ){
foreach( $this->bean->getProperties() as $property => $value ){
#set your criteria here for which fields
#should be from the one in the database and which should come from this copy
#this version simply takes all unset values in the current and sets them
#from the one in the database
if( !isset( $value ) && isset( $dupRow[$property] ) )
$this->bean->$property = $dupRow[$property];
}
$this->bean->id = $dupId['id']; #set id to the duplicates id
}
return true;
}
}
然后修改R::store()调用,如下所示:
\R::begin();
\R::store($oOpenStatBean);
\R::commit();
或
事务将导致“FOR UPDATE”子句锁定找到的行,或者在没有找到行的情况下,锁定索引中新行的位置,这样就不会出现并发问题
现在,这并不能解决一个用户更新记录时对另一个用户造成重创的问题,但这是一个完全不同的主题。您试图做的就是所谓的“升级”。试试谷歌搜索。我发现了关于它的讨论:它不像集成的ORM支持那样优雅。不,不幸的是,根据引用的讨论,真正的upsert功能被认为是业务逻辑,没有集成到红豆核心中。但是,您可以编写自己的自定义查询编写器或插件,将此类内容移动到orm中。关于写这些的信息在感谢中,但是我在一年多前问过这个问题。我知道。我有同样的问题,引用的讨论对您的原始问题进行了评论,这是唯一的来源。我一直在断断续续地寻找答案,通过提供我在这里能找到的唯一一个答案,有这个问题的人可以得到比“它不受支持”的答案多一点的答案,并看到一个工作,如果稍微不那么优雅,解决方案。我认为,若你们更新你们的答案,并写下不可能直接通过ORM完成的事情,那个么答案就会变得正确。
\R::transaction( function() use ( $oOpenStatBean ){ R::store( $oOpenStatBean ); } );