【问题标题】:PHP RedBean store bean if not exist onePHP RedBean 存储 bean 如果不存在一个
【发布时间】:2012-11-05 22:17:38
【问题描述】:

我有点困惑。我在直接邮件服务中积极使用 PHP RedBean 作为 ORM,但遇到了奇怪的情况 - 我有一个具有唯一键约束的表(即订阅者 ID、交付 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 获得相同的结果呢?

【问题讨论】:

标签: php mysql database orm redbean


【解决方案1】:

目前,我知道,如果 Redbean 不会发出一个

INSERT ON DUPLICATE KEY UPDATE

正如上面 cmets 中引用的讨论表明 Redbean 的开发人员认为 upsert 是一种会污染 ORM 界面的业务逻辑事物。话虽如此,如果根据Documentation 使用自定义查询编写器或插件来扩展 Redbean,这很可能是可以实现的。我没有尝试过,因为下面的方法很容易实现这种行为,而不会弄乱 ORM 的内部和插件,但是,它确实需要您使用事务和模型以及一些额外的查询。

基本上,在调用 R::store() 之前使用 R::transaction() 或 R::begin() 开始您的事务。然后在您的“FUSE”d 模型中,使用“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”子句锁定找到的行,或者在没有找到行的情况下锁定索引中新行所在的位置,这样您就没有并发性问题。

现在这不会解决一个用户更新记录的问题,但这是一个完全不同的话题。

【讨论】:

  • 它不像集成的 ORM 支持那样优雅的解决方案。
  • 不,不幸的是,根据引用的讨论,真正的 upsert 功能被认为是业务逻辑并且没有集成到 Redbean 核心中。但是,您可以编写自己的自定义查询编写器或插件,将这种东西移动到 orm.xml 中。有关编写这些的信息在documentation
  • 谢谢,不过一年多以前我就问过了。
  • 我知道。我有同样的问题,引用的讨论对你的原始问题发表评论是唯一的来源。我一直在断断续续地寻找答案,并认为,通过提供我在这里能找到的唯一一个,有这个问题的人可以获得的不仅仅是“它不受支持”的答案并看到一个有效的答案,如果稍微不那么优雅,解决方案。
  • 我认为如果你更新你的答案并写下不可能直接通过 ORM 回答将变得正确。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-08
  • 2011-06-11
  • 2014-01-02
  • 2019-04-28
相关资源
最近更新 更多