【问题标题】:phalcon automatic select before updatephalcon 更新前自动选择
【发布时间】:2014-01-19 13:45:37
【问题描述】:

我有一个这样的mysql表:

CREATE TABLE testtbl (
id INT(11) NOT NULL AUTO_INCREMENT,
field1 INT(11) NOT NULL DEFAULT '0',
field2 INT(11) NOT NULL DEFAULT '0',
PRIMARY KEY (id)
);
INSERT INTO testtbl (field1, field2) VALUES (1, 1);

我的 phalcon 项目中出现的模型是:

class Testtbl extends \Phalcon\Mvc\Model
{
    public $id;
    public $field1;
    public $field2;

    public function initialize() {
    $this->setConnectionService('db');
    $this->skipAttributes(array('field1'));
    }

    public function columnMap() {
        return array(
            'id' => 'id', 
            'field1' => 'field1', 
            'field2' => 'field2'
        );
    }
}

在我的控制器中,我有

$model = new Testtbl();
$model->id=1;
$model->field2 = 2;
if ($model->update() === false) {
    $msg = $model->getMessages();
    print_r($msg);
}

db 服务设置为将查询记录在文件中。 执行控制器的代码后,日志文件包含:

[][INFO] DESCRIBE `testtbl`
[][INFO] SELECT COUNT(*) "rowcount" FROM `testtbl` WHERE `id` = ?
[][INFO] UPDATE `testtbl` SET `field2` = ? WHERE `id` = ?

为什么更新前会有一个选择?

如何在没有任何事先选择的情况下进行更新并仍然使用 phalcon 的模型? (我可以直接在 Phalcon\Db\Adapter\Pdo\Mysql 中执行查询,但不想这样做。)

(如果我 -> 更新从 ->findFirst 接收的模型,则不会有任何 SELECT COUNT(*) "rowcount" ..,它仅在我直接进行更新时出现。)

【问题讨论】:

    标签: php mysql orm phalcon


    【解决方案1】:

    使用原始 SQL 查询(如 eBrian 所述)对您没有帮助。您必须在模型中额外提供手动元数据 (see here) 以防止选择语句。

    我使用 Testtbl 类在 metaData() 和 update() 函数中注释/注释进行了以下测试(在 postgresql 上)。

    <?php
    namespace Library\Storage;
    
    
    use Phalcon\Db\Adapter\Pdo\Postgresql;
    use Phalcon\Db\Column,
        Phalcon\Mvc\Model\MetaData;
    
    class Testtbl extends \Phalcon\Mvc\Model {
    
        public $id;
        public $field1;
        public $field2;
    
        public function metaData()
        {
            return array(
    
                //Every column in the mapped table
                MetaData::MODELS_ATTRIBUTES => array(
                    'id', 'field1', 'field2'
                ),
    
                //Every column part of the primary key
                MetaData::MODELS_PRIMARY_KEY => array(
                    'id'
                ),
    
                //Every column that isn't part of the primary key
                MetaData::MODELS_NON_PRIMARY_KEY => array(
                    'field1', 'field2'
                ),
    
                //Every column that doesn't allows null values
                MetaData::MODELS_NOT_NULL => array(
                    'id', 'field1', 'field2'
                ),
    
                //Every column and their data types
                MetaData::MODELS_DATA_TYPES => array(
                    'id' => Column::TYPE_INTEGER,
                    'field2' => Column::TYPE_INTEGER,
                    'field2' => Column::TYPE_INTEGER
                ),
    
                //The columns that have numeric data types
                MetaData::MODELS_DATA_TYPES_NUMERIC => array(
                    'id' => true,
                    'field1' => true,
                    'field2' => true,
                ),
    
                //The identity column, use boolean false if the model doesn't have
                //an identity column
                MetaData::MODELS_IDENTITY_COLUMN => 'id',
    
                //How every column must be bound/casted
                MetaData::MODELS_DATA_TYPES_BIND => array(
                    'id' => Column::BIND_PARAM_INT,
                    'field1' => Column::BIND_PARAM_INT,
                    'field2' => Column::BIND_PARAM_INT
                ),
    
                //Fields that must be ignored from INSERT SQL statements
                MetaData::MODELS_AUTOMATIC_DEFAULT_INSERT => array(
                    'id' => true
                ),
    
                //Fields that must be ignored from UPDATE SQL statements
                MetaData::MODELS_AUTOMATIC_DEFAULT_UPDATE => array(
                    'id' => true
                )
    
            );
        }
    
        public function initialize() {
            $this->setConnectionService('db');
            $this->skipAttributes(array('field1'));
        }
    
        public function columnMap() {
            return array(
                'id' => 'id',
                'field1' => 'field1',
                'field2' => 'field2'
            );
        }
    
        public function update($data=null, $whiteList=null) {
            if($this->id > 0) {
                $phql = 'UPDATE testtbl SET field1 = :f1, field2 = :f2 WHERE id = '.$this->id;
                /** @var Postgresql $db */
                $db = $this->getDI()->get('db');
                $db->execute($phql, array('f1' => $this->field1, 'f2' => $this->field2));
            } else {
                throw new \UnexpectedValueException('ID is required for update');
            }
            return TRUE;
        }
    }
    

    我进行了 4 次测试运行以查看我的 PostgreSQL 日志:

    没有使用 Model->update() 的元数据

    2014-01-30 20:30:11 CET LOG:  statement: SET search_path TO 'public'
    2014-01-30 20:30:11 CET LOG:  execute pdo_stmt_00000001: SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END FROM information_schema.tables WHERE table_schema = 'public' AND table_name='testtbl'
    2014-01-30 20:30:11 CET LOG:  statement: DEALLOCATE pdo_stmt_00000001
    2014-01-30 20:30:11 CET LOG:  execute pdo_stmt_00000002: SELECT DISTINCT c.column_name AS Field, c.data_type AS Type, c.character_maximum_length AS Size, c.numeric_precision AS NumericSize, c.numeric_scale AS NumericScale, c.is_nullable AS Null, CASE WHEN pkc.column_name NOTNULL THEN 'PRI' ELSE '' END AS Key, CASE WHEN c.data_type LIKE '%int%' AND c.column_default LIKE '%nextval%' THEN 'auto_increment' ELSE '' END AS Extra, c.ordinal_position AS Position FROM information_schema.columns c LEFT JOIN ( SELECT kcu.column_name, kcu.table_name, kcu.table_schema FROM information_schema.table_constraints tc INNER JOIN information_schema.key_column_usage kcu on (kcu.constraint_name = tc.constraint_name and kcu.table_name=tc.table_name and kcu.table_schema=tc.table_schema) WHERE tc.constraint_type='PRIMARY KEY') pkc ON (c.column_name=pkc.column_name AND c.table_schema = pkc.table_schema AND c.table_name=pkc.table_name) WHERE c.table_schema='public' AND c.table_name='testtbl' ORDER BY c.ordinal_position
    2014-01-30 20:30:11 CET LOG:  statement: DEALLOCATE pdo_stmt_00000002
    2014-01-30 20:30:11 CET LOG:  execute pdo_stmt_00000003: SELECT COUNT(*) "rowcount" FROM "testtbl" WHERE "id" = $1
    2014-01-30 20:30:11 CET DETAIL:  parameters: $1 = '1'
    

    没有使用覆盖模型的元数据->update()

    2014-01-30 20:28:38 CET LOG:  statement: SET search_path TO 'public'
    2014-01-30 20:28:38 CET LOG:  execute pdo_stmt_00000001: SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END FROM information_schema.tables WHERE table_schema = 'public' AND table_name='testtbl'
    2014-01-30 20:28:38 CET LOG:  statement: DEALLOCATE pdo_stmt_00000001
    2014-01-30 20:28:38 CET LOG:  execute pdo_stmt_00000002: SELECT DISTINCT c.column_name AS Field, c.data_type AS Type, c.character_maximum_length AS Size, c.numeric_precision AS NumericSize, c.numeric_scale AS NumericScale, c.is_nullable AS Null, CASE WHEN pkc.column_name NOTNULL THEN 'PRI' ELSE '' END AS Key, CASE WHEN c.data_type LIKE '%int%' AND c.column_default LIKE '%nextval%' THEN 'auto_increment' ELSE '' END AS Extra, c.ordinal_position AS Position FROM information_schema.columns c LEFT JOIN ( SELECT kcu.column_name, kcu.table_name, kcu.table_schema FROM information_schema.table_constraints tc INNER JOIN information_schema.key_column_usage kcu on (kcu.constraint_name = tc.constraint_name and kcu.table_name=tc.table_name and kcu.table_schema=tc.table_schema) WHERE tc.constraint_type='PRIMARY KEY') pkc ON (c.column_name=pkc.column_name AND c.table_schema = pkc.table_schema AND c.table_name=pkc.table_name) WHERE c.table_schema='public' AND c.table_name='testtbl' ORDER BY c.ordinal_position
    2014-01-30 20:28:38 CET LOG:  statement: DEALLOCATE pdo_stmt_00000002
    2014-01-30 20:28:38 CET LOG:  execute pdo_stmt_00000003: UPDATE testtbl SET field1 = $1, field2 = $2 WHERE id = 1
    2014-01-30 20:28:38 CET DETAIL:  parameters: $1 = '1', $2 = '2'
    2014-01-30 20:28:38 CET LOG:  statement: DEALLOCATE pdo_stmt_00000003
    

    使用 Model->update() 手动元数据

    2014-01-30 20:26:12 CET LOG:  statement: SET search_path TO 'public'
    2014-01-30 20:26:12 CET LOG:  execute pdo_stmt_00000001: SELECT COUNT(*) "rowcount" FROM "testtbl" WHERE "id" = $1
    2014-01-30 20:26:12 CET DETAIL:  parameters: $1 = '1'
    2014-01-30 20:26:12 CET LOG:  statement: DEALLOCATE pdo_stmt_00000001
    2014-01-30 20:26:12 CET LOG:  execute pdo_stmt_00000002: UPDATE "testtbl" SET "field2" = $1 WHERE "id" = $2
    2014-01-30 20:26:12 CET DETAIL:  parameters: $1 = '2', $2 = '1'
    2014-01-30 20:26:12 CET LOG:  statement: DEALLOCATE pdo_stmt_00000002
    

    使用覆盖模型的手动元数据->update()

    2014-01-30 20:23:14 CET LOG:  statement: SET search_path TO 'public'
    2014-01-30 20:23:14 CET LOG:  execute pdo_stmt_00000001: UPDATE testtbl SET field1 = $1, field2 = $2 WHERE id = 1
    2014-01-30 20:23:14 CET DETAIL:  parameters: $1 = '1', $2 = '2'
    2014-01-30 20:23:14 CET LOG:  statement: DEALLOCATE pdo_stmt_00000001
    

    如您所见,发出单个更新查询的唯一方法是提供完整的元数据将原始 SQL 语句与 pdo 一起使用。

    【讨论】:

      【解决方案2】:

      如果没有原始 SQL,这是不可能的,因为如果不需要,Phalcon 不会更新记录。由于您正在创建一个新对象并使用主键填充它,因此您强制 Phalcon 首先检查是否存在与您设置的 id 匹配的实际记录。

      这让您有以下选择:

      • 使用原始 SQL
      • findFirst()记录,修改内容,调用update()或save()

      编辑:

      $model = new Testtbl();
      $model->setReadConnectionService('master');
      $model->id=1;
      $model->field2 = 2;
      $model->update();
      

      这将确保描述语句(如果您没有模型元数据)和行计数针对具有最新数据的主数据库。

      这能解决您的问题吗?

      【讨论】:

      • findFirst 对我来说不是一个选项,因为它仍然会进行选择。当模型在模型中使用 setReadConnectionService('slave_server') 和 setWriteConnectionService('master_server') 时,该问题具有另一种含义。如果从服务器落后于主服务器(滞后),无论出于何种原因,更新将永远丢失,因为它们不会进入复制 binlog。
      • 是的,这确实为问题增加了新的动力。对不起,我没有答案。我要撤回这个答案。
      • 不要 :) 到目前为止是我从任何地方得到的唯一答案
      • 当我看到 phalcon 标签问题时,我总是尝试回答它们。我想帮你解决这个问题。我进一步研究了它,你总是可以选择 $model->setReadConnectionService('master');在您创建新模型之后。我更新了我对这个建议的回答。
      【解决方案3】:

      在 phalcon 文档中有对验证消息 InvalidUpdateAttempt 的引用。 wich 在尝试更新记录但不存在时产生。 我还没有找到任何方法来抑制它的触发器,看起来没有,也许你会有更多的运气。

      【讨论】:

        猜你喜欢
        • 2020-09-06
        • 2019-12-19
        • 1970-01-01
        • 1970-01-01
        • 2020-06-16
        • 2013-04-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多