我有点困惑。我在直接邮件服务中积极使用PHP RedBean作为ORM,但遇到了奇怪的情况-我有一个具有唯一键约束的表(即,subscriber_id,delivery_id)和两个将数据写入该表的脚本。
有正在插入或更新表的源代码:
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的中间阶段。话虽如此,如果按照Documentation使用自定义查询编写器或插件扩展Redbean是最有可能实现的。我没有尝试过,因为下面的方法很容易实现此行为而不会弄乱ORM的内部和插件,但是,它确实需要您使用事务和模型以及几个额外的查询。
基本上,在调用R::store()之前,先使用R::transaction()或R::begin()启动事务。然后在您的“FUSE”模型中,使用“update” FUSE方法运行查询,以检查是否重复并在锁定必要行的同时检索现有ID(即SELECT FOR UPDATE)。如果没有返回ID,则表示您很好,只需照常进行常规模型验证(或缺少模型验证),然后返回即可。如果找到一个id,只需将$ this-> bean-> id设置为返回值,Redbean将更新而不是INSERT。因此,使用这样的模型:
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();
或者
\R::transaction( function() use ( $oOpenStatBean ){ R::store( $oOpenStatBean ); } );
该事务将导致“FOR UPDATE”子句锁定找到的行,或者在未找到行的情况下,锁定索引中新行将要到达的位置,以免出现并发问题。
现在,这不会解决一个用户更新记录破坏另一个用户的问题,但这是一个完全不同的主题。