PHP RedBean хранит bean-компонент, если он не существует

Я немного запутался. Я активно использую 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?


person Ivan Velichko    schedule 17.11.2012    source источник
comment
То, что вы пытаетесь сделать, называется upsert. Попробуйте погуглить для этого. Я нашел это обсуждение по этому поводу: github.com/gabordemooij/redbean/issues/160   -  person Botond Balázs    schedule 17.11.2012


Ответы (1)


Текущий, что я знаю, если Redbean не выдаст

INSERT ON DUPLICATE KEY UPDATE

поскольку обсуждение этого, приведенное в комментариях выше, указывает на то, что разработчик Redbean считает upsert бизнес-логикой, которая загрязняет интерфазу ORM. При этом, скорее всего, это достижимо, если кто-то расширит Redbean с помощью специального модуля записи запросов или плагина в соответствии с документацией. Я не пробовал это, потому что приведенный ниже метод легко обеспечивает такое поведение, не возясь с внутренними компонентами и плагинами ORM, однако он требует использования транзакций и моделей, а также пары дополнительных запросов.

По сути, начинайте транзакцию либо с R::transaction(), либо с R::begin() перед вызовом R::store(). Затем в вашей модели «FUSE» d используйте метод FUSE «обновить» для запуска запроса, который проверяет наличие дублирования и извлекает существующий идентификатор при блокировке необходимых строк (т.е. SELECT FOR UPDATE). Если идентификатор не возвращается, все в порядке, и вы просто позволяете своей обычной проверке модели (или ее отсутствию) продолжаться как обычно и возвращаться. Если идентификатор найден, просто установите $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();

or

\R::transaction( function() use ( $oOpenStatBean ){ R::store( $oOpenStatBean ); } );

Транзакция приведет к тому, что предложение «FOR UPDATE» заблокирует найденную строку или, в случае, если строка не будет найдена, заблокирует места в индексе, куда будет помещена ваша новая строка, чтобы у вас не было проблем с параллелизмом.

Теперь это не решит проблему, когда один пользователь обновляет запись, уничтожая другую, но это совсем другая тема.

person Reid Johnson    schedule 01.09.2014
comment
Это не такое элегантное решение, как встроенная поддержка ORM. - person Ivan Velichko; 02.09.2014
comment
Спасибо, но я спрашивал об этом больше года назад. - person Ivan Velichko; 02.09.2014
comment
Я знаю. У меня была такая же проблема, и цитируемое обсуждение, прокомментированное к вашему первоначальному вопросу, было единственным источником. Я продолжал искать ответ и полагал, что, предоставив единственный ответ, который я смог найти здесь, те, у кого есть этот вопрос, могли бы получить немного больше, чем просто ответ не поддерживается, и увидеть работающий, если чуть менее элегантное решение. - person Reid Johnson; 02.09.2014
comment
Я думаю, если вы обновите свой ответ и напишите о невозможности сделать это напрямую через ORM, ответ станет правильным. - person Ivan Velichko; 02.09.2014
comment
Я обновил ответ, чтобы уточнить, что предлагаемое решение не находится внутри ORM, а также включить информацию о возможности создания плагина или пользовательского автора запросов. Спасибо, что приняли этот ответ так долго после факта. Я надеюсь, что наличие его здесь поможет другим в их стремлении использовать Redbean. - person Reid Johnson; 03.09.2014