如何在 Yii 中根据查询类型使用不同的连接


How to use different connections according to query type in Yii

我所有的读取都应该转到一个数据库连接我所有的写都应该转到另一个连接

如何在 Yii 中完成此操作,同时最少更改核心库的代码?

有时(如评论中所述(我需要能够控制每种模型类型的连接,因此也可以阅读到大师。

我编写了一个应用程序,其中主管理面板可用于创建和管理多个面向客户的"实例",因此需要将主应用程序中运行的查询"定向"到任何一个特定于实例的数据库。我将说明我首先做了什么(没有你的目标那么苛刻(的精简版本,然后介绍一种更强大的方法。

对所有查询使用多个数据库

将查询定向到事先指定的数据库很容易:只需覆盖CActiveRecord::getDbConnection方法即可。我所做的可以精简为:

abstract class InstanceActiveRecord extends CActiveRecord {
    public static $dbConnection = null;
    public function getDbConnection() {
        if (self::$dbConnection === null) {
            throw new CException('Database connection must be defined to work with instance records.');
        }
        return self::$dbConnection;
    }
}

因此,如果您想将所有操作定向到特定的数据库,您只需从InstanceActiveRecord而不是CActiveRecord派生ActiveRecord模型,然后只需执行InstanceActiveRecord::dbConnection = $connection即可。

使用多个数据库,并根据查询类型自动选择

为此,您需要更深入地了解CActiveRecord.事实证明,getDbConnection主要由getCommandBuilder使用,而又是所有删除/更新/插入族调用的方法。所以我们需要将某种上下文从这些函数传递到 getDbConnection ,在那里我们将选择要使用的连接。

为此,我们将不得不覆盖这些系列中的所有方法,因此合理的方法可能是:

第 1 步。将可选参数添加到getDbConnection并覆盖它以根据参数值返回所需的任何连接。最简单的是这样的:

public function getDbConnection($writeContext = null) {
    if ($writeContext === null) {
        return parent::getDbConnection(); // to make sure nothing will ever break
    }
    // You need to get the values for $writeDb and $readDb in here somehow,
    // but this can be as trivially easy as you like (e.g. public static prop)
    return $writeContext ? $writeDb : $readDb;
}

第 2 步。将可选参数添加到具有相同语义的getCommandBuilder,并覆盖它以转发值:

public function getCommandBuilder($writeContext = null) {
    return $this->getDbConnection($writeContext)->getSchema()->getCommandBuilder();
}

第 3 步。找到getCommandBuilder的所有调用站点(会有一堆(和getDbConnection(在我查看时,getCommandBuilder内部的站点仅多 2 个(并覆盖它们以适当地指定读/写上下文。例:

public function deleteAll($condition='',$params=array()) {
    Yii::trace(get_class($this).'.deleteAll()','system.db.ar.CActiveRecord');
    // Just need to add the (true) value here to specify write context:
    $builder=$this->getCommandBuilder(true);
    $criteria=$builder->createCriteria($condition,$params);
    $command=$builder->createDeleteCommand($this->getTableSchema(),$criteria);
    return $command->execute();
}

在此之后,您应该准备好了。没有什么能阻止您制作比此处所示的true/false选项更复杂的上下文选择机制,概念是相同的。

实际关切

虽然所有这些都将完美地实现既定目标,但这种方法的可维护性仍然存在问题。

确实,走这条路会涉及大量从CActiveRecord复制/粘贴代码,如果以后有机会将您的应用程序移动到框架的更高版本,这并不理想;为此,您将被迫将子类与最新版本的CActiveRecord同步。

若要迁移此问题并使将来的生活更轻松,可以考虑以下方法:

  1. 与其复制/粘贴并仅覆盖CActiveRecord的一部分,不如精确复制(当然减去属性(CActiveRecord并在那里执行更改。换句话说,即使是那些您打算重写的方法,也要复制。
  2. 执行上述更改。请记住,这涉及覆盖getDbConnection,并且只对十几个或二十个其他地方进行非常小的编辑
  3. 使模型扩展生成的类。

现在,当升级到更高版本的 Yii 时,您需要再次使您的类与 CActiveRecord 同步。启动您最喜欢的差异工具,并将您的类与目标版本的CActiveRecord进行比较。diff 工具将只显示getDbConnection和次要的编辑,以及对 Yii 核心中CActiveRecord所做的任何更改。将这些其他更改复制到您的班级中。问题在5分钟内解决。