国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

活動(dòng)記錄(Active Record): 活動(dòng)記錄對(duì)象關(guān)系映射(ORM),檢索和操作記錄、定義關(guān)聯(lián)關(guān)系

2018-02-24 15:40 更新

Active Record

注意:該章節(jié)還在開發(fā)中。

Active Record?(活動(dòng)記錄,以下簡(jiǎn)稱AR)提供了一個(gè)面向?qū)ο蟮慕涌冢?用以訪問數(shù)據(jù)庫中的數(shù)據(jù)。一個(gè) AR 類關(guān)聯(lián)一張數(shù)據(jù)表, 每個(gè) AR 對(duì)象對(duì)應(yīng)表中的一行,對(duì)象的屬性(即 AR 的特性Attribute)映射到數(shù)據(jù)行的對(duì)應(yīng)列。 一條活動(dòng)記錄(AR對(duì)象)對(duì)應(yīng)數(shù)據(jù)表的一行,AR對(duì)象的屬性則映射該行的相應(yīng)列。 您可以直接以面向?qū)ο蟮姆绞絹聿倏v數(shù)據(jù)表中的數(shù)據(jù),媽媽再不用擔(dān)心我需要寫原生 SQL 語句啦。

例如,假定?Customer?AR 類關(guān)聯(lián)著?customer?表,且該類的?name?屬性代表?customer?表的?name?列。 你可以寫以下代碼來哉customer?表里插入一行新的記錄:

用 AR 而不是原生的 SQL 語句去執(zhí)行數(shù)據(jù)庫查詢,可以調(diào)用直觀方法來實(shí)現(xiàn)相同目標(biāo)。如,調(diào)用 yii\db\ActiveRecord::save() 方法將執(zhí)行插入或更新輪詢,將在該 AR 類關(guān)聯(lián)的數(shù)據(jù)表新建或更新一行數(shù)據(jù):

$customer = new Customer();
$customer->name = 'Qiang';
$customer->save();  // 一行新數(shù)據(jù)插入 customer 表

上面的代碼和使用下面的原生 SQL 語句是等效的,但顯然前者更直觀, 更不易出錯(cuò),并且面對(duì)不同的數(shù)據(jù)庫系統(tǒng)(DBMS, Database Management System)時(shí)更不容易產(chǎn)生兼容性問題。

$db->createCommand('INSERT INTO customer (name) VALUES (:name)', [
    ':name' => 'Qiang',
])->execute();

下面是所有目前被 Yii 的 AR 功能所支持的數(shù)據(jù)庫列表:

  • MySQL 4.1 及以上:通過 yii\db\ActiveRecord
  • PostgreSQL 7.3 及以上:通過 yii\db\ActiveRecord
  • SQLite 2 和 3:通過 yii\db\ActiveRecord
  • Microsoft SQL Server 2010 及以上:通過 yii\db\ActiveRecord
  • Oracle: 通過 yii\db\ActiveRecord
  • CUBRID 9.1 及以上:通過 yii\db\ActiveRecord
  • Sphinx:通過 yii\sphinx\ActiveRecord,需求?yii2-sphinx?擴(kuò)展
  • ElasticSearch:通過 yii\elasticsearch\ActiveRecord,需求?yii2-elasticsearch?擴(kuò)展
  • Redis 2.6.12 及以上:通過 yii\redis\ActiveRecord,需求?yii2-redis?擴(kuò)展
  • MongoDB 1.3.0 及以上:通過 yii\mongodb\ActiveRecord,需求?yii2-mongodb?擴(kuò)展

如你所見,Yii 不僅提供了對(duì)關(guān)系型數(shù)據(jù)庫的 AR 支持,還提供了 NoSQL 數(shù)據(jù)庫的支持。 在這個(gè)教程中,我們會(huì)主要描述對(duì)關(guān)系型數(shù)據(jù)庫的 AR 用法。 然而,絕大多數(shù)的內(nèi)容在 NoSQL 的 AR 里同樣適用。

聲明 AR 類

要想聲明一個(gè) AR 類,你需要擴(kuò)展 yii\db\ActiveRecord 基類, 并實(shí)現(xiàn)?tableName?方法,返回與之相關(guān)聯(lián)的的數(shù)據(jù)表的名稱:

namespace app\models;

use yii\db\ActiveRecord;

class Customer extends ActiveRecord
{
    /**
     * @return string 返回該AR類關(guān)聯(lián)的數(shù)據(jù)表名
     */
    public static function tableName()
    {
        return 'customer';
    }
}

訪問列數(shù)據(jù)

AR 把相應(yīng)數(shù)據(jù)行的每一個(gè)字段映射為 AR 對(duì)象的一個(gè)個(gè)特性變量(Attribute) 一個(gè)特性就好像一個(gè)普通對(duì)象的公共屬性一樣(public property)。 特性變量的名稱和對(duì)應(yīng)字段的名稱是一樣的,且大小姓名。

使用以下語法讀取列的值:

// "id" 和 "mail" 是 $customer 對(duì)象所關(guān)聯(lián)的數(shù)據(jù)表的對(duì)應(yīng)字段名
$id = $customer->id;
$email = $customer->email;

要改變列值,只要給關(guān)聯(lián)屬性賦新值并保存對(duì)象即可:

$customer->email = 'james@example.com';
$customer->save();

建立數(shù)據(jù)庫連接

AR 用一個(gè) yii\db\Connection 對(duì)象與數(shù)據(jù)庫交換數(shù)據(jù)。 默認(rèn)的,它使用?db?組件作為其連接對(duì)象。詳見數(shù)據(jù)庫基礎(chǔ)章節(jié), 你可以在應(yīng)用程序配置文件中設(shè)置下?db?組件,就像這樣,

return [
    'components' => [
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=testdb',
            'username' => 'demo',
            'password' => 'demo',
        ],
    ],
];

如果在你的應(yīng)用中應(yīng)用了不止一個(gè)數(shù)據(jù)庫,且你需要給你的 AR 類使用不同的數(shù)據(jù)庫鏈接(DB connection) ,你可以覆蓋掉 yii\db\ActiveRecord::getDb() 方法:

class Customer extends ActiveRecord
{
    // ...

    public static function getDb()
    {
        return \Yii::$app->db2;  // 使用名為 "db2" 的應(yīng)用組件
    }
}

查詢數(shù)據(jù)

AR 提供了兩種方法來構(gòu)建 DB 查詢并向 AR 實(shí)例里填充數(shù)據(jù):

  • yii\db\ActiveRecord::find()
  • yii\db\ActiveRecord::findBySql()

以上兩個(gè)方法都會(huì)返回 yii\db\ActiveQuery 實(shí)例,該類繼承自yii\db\Query, 因此,他們都支持同一套靈活且強(qiáng)大的 DB 查詢方法,如where(),join(),orderBy(),等等。 下面的這些案例展示了一些可能的玩法:

// 取回所有活躍客戶(狀態(tài)為 *active* 的客戶)并以他們的 ID 排序:
$customers = Customer::find()
    ->where(['status' => Customer::STATUS_ACTIVE])
    ->orderBy('id')
    ->all();

// 返回ID為1的客戶:
$customer = Customer::find()
    ->where(['id' => 1])
    ->one();

// 取回活躍客戶的數(shù)量:
$count = Customer::find()
    ->where(['status' => Customer::STATUS_ACTIVE])
    ->count();

// 以客戶ID索引結(jié)果集:
$customers = Customer::find()->indexBy('id')->all();
// $customers 數(shù)組以 ID 為索引

// 用原生 SQL 語句檢索客戶:
$sql = 'SELECT * FROM customer';
$customers = Customer::findBySql($sql)->all();

小技巧:在上面的代碼中,Customer::STATUS_ACTIVE?是一個(gè)在?Customer?類里定義的常量。(譯注:這種常量的值一般都是tinyint)相較于直接在代碼中寫死字符串或數(shù)字,使用一個(gè)更有意義的常量名稱是一種更好的編程習(xí)慣。

有兩個(gè)快捷方法:findOne?和?findAll()?用來返回一個(gè)或者一組ActiveRecord實(shí)例。前者返回第一個(gè)匹配到的實(shí)例,后者返回所有。 例如:

// 返回 id 為 1 的客戶
$customer = Customer::findOne(1);

// 返回 id 為 1 且狀態(tài)為 *active* 的客戶
$customer = Customer::findOne([
    'id' => 1,
    'status' => Customer::STATUS_ACTIVE,
]);

// 返回id為1、2、3的一組客戶
$customers = Customer::findAll([1, 2, 3]);

// 返回所有狀態(tài)為 "deleted" 的客戶
$customer = Customer::findAll([
    'status' => Customer::STATUS_DELETED,
]);

以數(shù)組形式獲取數(shù)據(jù)

有時(shí)候,我們需要處理很大量的數(shù)據(jù),這時(shí)可能需要用一個(gè)數(shù)組來存儲(chǔ)取到的數(shù)據(jù), 從而節(jié)省內(nèi)存。你可以用?asArray()?函數(shù)做到這一點(diǎn):

// 以數(shù)組而不是對(duì)象形式取回客戶信息:
$customers = Customer::find()
    ->asArray()
    ->all();
// $customers 的每個(gè)元素都是鍵值對(duì)數(shù)組

批量獲取數(shù)據(jù)

在?Query Builder(查詢構(gòu)造器)?里,我們已經(jīng)解釋了當(dāng)需要從數(shù)據(jù)庫中查詢大量數(shù)據(jù)時(shí),你可以用?batch query(批量查詢)來限制內(nèi)存的占用。 你可能也想在 AR 里使用相同的技巧,比如這樣……

// 一次提取 10 個(gè)客戶信息
foreach (Customer::find()->batch(10) as $customers) {
    // $customers 是 10 個(gè)或更少的客戶對(duì)象的數(shù)組
}
// 一次提取 10 個(gè)客戶并一個(gè)一個(gè)地遍歷處理
foreach (Customer::find()->each(10) as $customer) {
    // $customer 是一個(gè) ”Customer“ 對(duì)象
}
// 貪婪加載模式的批處理查詢
foreach (Customer::find()->with('orders')->each() as $customer) {
}

操作數(shù)據(jù)

AR 提供以下方法插入、更新和刪除與 AR 對(duì)象關(guān)聯(lián)的那張表中的某一行:

  • yii\db\ActiveRecord::save()
  • yii\db\ActiveRecord::insert()
  • yii\db\ActiveRecord::update()
  • yii\db\ActiveRecord::delete()

AR 同時(shí)提供了一下靜態(tài)方法,可以應(yīng)用在與某 AR 類所關(guān)聯(lián)的整張表上。 用這些方法的時(shí)候千萬要小心,因?yàn)樗麄冏饔糜谡麖埍恚?比如,deleteAll()?會(huì)刪除掉表里所有的記錄。

  • yii\db\ActiveRecord::updateCounters()
  • yii\db\ActiveRecord::updateAll()
  • yii\db\ActiveRecord::updateAllCounters()
  • yii\db\ActiveRecord::deleteAll()

下面的這些例子里,詳細(xì)展現(xiàn)了如何使用這些方法:

// 插入新客戶的記錄
$customer = new Customer();
$customer->name = 'James';
$customer->email = 'james@example.com';
$customer->save();  // 等同于 $customer->insert();

// 更新現(xiàn)有客戶記錄
$customer = Customer::findOne($id);
$customer->email = 'james@example.com';
$customer->save();  // 等同于 $customer->update();

// 刪除已有客戶記錄
$customer = Customer::findOne($id);
$customer->delete();

// 刪除多個(gè)年齡大于20,性別為男(Male)的客戶記錄
Customer::deleteAll('age > :age AND gender = :gender', [':age' => 20, ':gender' => 'M']);

// 所有客戶的age(年齡)字段加1:
Customer::updateAllCounters(['age' => 1]);

須知:save()?方法會(huì)調(diào)用?insert()?和?update()?中的一個(gè), 用哪個(gè)取決于當(dāng)前 AR 對(duì)象是不是新對(duì)象(在函數(shù)內(nèi)部,他會(huì)檢查 yii\db\ActiveRecord::isNewRecord 的值)。 若 AR 對(duì)象是由?new?操作符 初始化出來的,save()?方法會(huì)在表里插入一條數(shù)據(jù); 如果一個(gè) AR 是由?find()?方法獲取來的, 則?save()?會(huì)更新表里的對(duì)應(yīng)行記錄。

數(shù)據(jù)輸入與有效性驗(yàn)證

由于AR繼承自yii\base\Model,所以它同樣也支持Model的數(shù)據(jù)輸入、驗(yàn)證等特性。例如,你可以聲明一個(gè)rules方法用來覆蓋掉yii\base\Model::rules()里的;你也可以給AR實(shí)例批量賦值;你也可以通過調(diào)用yii\base\Model::validate()執(zhí)行數(shù)據(jù)驗(yàn)證。

當(dāng)你調(diào)用?save()、insert()、update()?這三個(gè)方法時(shí),會(huì)自動(dòng)調(diào)用yii\base\Model::validate()方法。如果驗(yàn)證失敗,數(shù)據(jù)將不會(huì)保存進(jìn)數(shù)據(jù)庫。

下面的例子演示了如何使用AR 獲取/驗(yàn)證用戶輸入的數(shù)據(jù)并將他們保存進(jìn)數(shù)據(jù)庫:

// 新建一條記錄
$model = new Customer;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
    // 獲取用戶輸入的數(shù)據(jù),驗(yàn)證并保存
}

// 更新主鍵為$id的AR
$model = Customer::findOne($id);
if ($model === null) {
    throw new NotFoundHttpException;
}
if ($model->load(Yii::$app->request->post()) && $model->save()) {
    // 獲取用戶輸入的數(shù)據(jù),驗(yàn)證并保存
}

讀取默認(rèn)值

你的表列也許定義了默認(rèn)值。有時(shí)候,你可能需要在使用web表單的時(shí)候給AR預(yù)設(shè)一些值。如果你需要這樣做,可以在顯示表單內(nèi)容前通過調(diào)用loadDefaultValues()方法來實(shí)現(xiàn):?php $customer = new Customer(); $customer->loadDefaultValues(); // ... 渲染 $customer 的 HTML 表單 ...?

AR的生命周期

理解AR的生命周期對(duì)于你操作數(shù)據(jù)庫非常重要。生命周期通常都會(huì)有些典型的事件存在。對(duì)于開發(fā)AR的behaviors來說非常有用。

當(dāng)你實(shí)例化一個(gè)新的AR對(duì)象時(shí),我們將獲得如下的生命周期:

  1. constructor
  2. yii\db\ActiveRecord::init(): 會(huì)觸發(fā)一個(gè) yii\db\ActiveRecord::EVENT_INIT 事件

當(dāng)你通過 yii\db\ActiveRecord::find() 方法查詢數(shù)據(jù)時(shí),每個(gè)AR實(shí)例都將有以下生命周期:

  1. constructor
  2. yii\db\ActiveRecord::init(): 會(huì)觸發(fā)一個(gè) yii\db\ActiveRecord::EVENT_INIT 事件
  3. yii\db\ActiveRecord::afterFind(): 會(huì)觸發(fā)一個(gè) yii\db\ActiveRecord::EVENT_AFTER_FIND 事件

當(dāng)通過 yii\db\ActiveRecord::save() 方法寫入或者更新數(shù)據(jù)時(shí), 我們將獲得如下生命周期:

  1. yii\db\ActiveRecord::beforeValidate(): 會(huì)觸發(fā)一個(gè) yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE 事件
  2. yii\db\ActiveRecord::afterValidate(): 會(huì)觸發(fā)一個(gè) yii\db\ActiveRecord::EVENT_AFTER_VALIDATE 事件
  3. yii\db\ActiveRecord::beforeSave(): 會(huì)觸發(fā)一個(gè) yii\db\ActiveRecord::EVENT_BEFORE_INSERT 或 yii\db\ActiveRecord::EVENT_BEFORE_UPDATE 事件
  4. 執(zhí)行實(shí)際的數(shù)據(jù)寫入或更新
  5. yii\db\ActiveRecord::afterSave(): 會(huì)觸發(fā)一個(gè) yii\db\ActiveRecord::EVENT_AFTER_INSERT 或 yii\db\ActiveRecord::EVENT_AFTER_UPDATE 事件

最后,當(dāng)調(diào)用 yii\db\ActiveRecord::delete() 刪除數(shù)據(jù)時(shí), 我們將獲得如下生命周期:

  1. yii\db\ActiveRecord::beforeDelete(): 會(huì)觸發(fā)一個(gè) yii\db\ActiveRecord::EVENT_BEFORE_DELETE 事件
  2. 執(zhí)行實(shí)際的數(shù)據(jù)刪除
  3. yii\db\ActiveRecord::afterDelete(): 會(huì)觸發(fā)一個(gè) yii\db\ActiveRecord::EVENT_AFTER_DELETE 事件

查詢關(guān)聯(lián)的數(shù)據(jù)

使用 AR 方法也可以查詢數(shù)據(jù)表的關(guān)聯(lián)數(shù)據(jù)(如,選出表A的數(shù)據(jù)可以拉出表B的關(guān)聯(lián)數(shù)據(jù))。 有了 AR, 返回的關(guān)聯(lián)數(shù)據(jù)連接就像連接關(guān)聯(lián)主表的 AR 對(duì)象的屬性一樣。

建立關(guān)聯(lián)關(guān)系后,通過?$customer->orders?可以獲取 一個(gè)?Order?對(duì)象的數(shù)組,該數(shù)組代表當(dāng)前客戶對(duì)象的訂單集。

定義關(guān)聯(lián)關(guān)系使用一個(gè)可以返回 yii\db\ActiveQuery 對(duì)象的 getter 方法, yii\db\ActiveQuery對(duì)象有關(guān)聯(lián)上下文的相關(guān)信息,因此可以只查詢關(guān)聯(lián)數(shù)據(jù)。

例如:

class Customer extends \yii\db\ActiveRecord
{
    public function getOrders()
    {
        // 客戶和訂單通過 Order.customer_id -> id 關(guān)聯(lián)建立一對(duì)多關(guān)系
        return $this->hasMany(Order::className(), ['customer_id' => 'id']);
    }
}

class Order extends \yii\db\ActiveRecord
{
    // 訂單和客戶通過 Customer.id -> customer_id 關(guān)聯(lián)建立一對(duì)一關(guān)系
    public function getCustomer()
    {
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
    }
}

以上使用了 yii\db\ActiveRecord::hasMany() 和 yii\db\ActiveRecord::hasOne() 方法。 以上兩例分別是關(guān)聯(lián)數(shù)據(jù)多對(duì)一關(guān)系和一對(duì)一關(guān)系的建模范例。 如,一個(gè)客戶有很多訂單,一個(gè)訂單只歸屬一個(gè)客戶。 兩個(gè)方法都有兩個(gè)參數(shù)并返回 yii\db\ActiveQuery 對(duì)象。

  • $class:關(guān)聯(lián)模型類名,它必須是一個(gè)完全合格的類名。
  • $link: 兩個(gè)表的關(guān)聯(lián)列,應(yīng)為鍵值對(duì)數(shù)組的形式。 數(shù)組的鍵是?$class?關(guān)聯(lián)表的列名, 而數(shù)組值是關(guān)聯(lián)類 $class 的列名。 基于表外鍵定義關(guān)聯(lián)關(guān)系是最佳方法。

建立關(guān)聯(lián)關(guān)系后,獲取關(guān)聯(lián)數(shù)據(jù)和獲取組件屬性一樣簡(jiǎn)單, 執(zhí)行以下相應(yīng)getter方法即可:

// 取得客戶的訂單
$customer = Customer::findOne(1);
$orders = $customer->orders; // $orders 是 Order 對(duì)象數(shù)組

以上代碼實(shí)際執(zhí)行了以下兩條 SQL 語句:

SELECT * FROM customer WHERE id=1;
SELECT * FROM order WHERE customer_id=1;

提示:再次用表達(dá)式?$customer->orders將不會(huì)執(zhí)行第二次 SQL 查詢, SQL 查詢只在該表達(dá)式第一次使用時(shí)執(zhí)行。 數(shù)據(jù)庫訪問只返回緩存在內(nèi)部前一次取回的結(jié)果集,如果你想查詢新的 關(guān)聯(lián)數(shù)據(jù),先要注銷現(xiàn)有結(jié)果集:unset($customer->orders);。

有時(shí)候需要在關(guān)聯(lián)查詢中傳遞參數(shù),如不需要返回客戶全部訂單, 只需要返回購買金額超過設(shè)定值的大訂單, 通過以下getter方法聲明一個(gè)關(guān)聯(lián)數(shù)據(jù)?bigOrders?:

class Customer extends \yii\db\ActiveRecord
{
    public function getBigOrders($threshold = 100)
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])
            ->where('subtotal > :threshold', [':threshold' => $threshold])
            ->orderBy('id');
    }
}

hasMany()?返回 yii\db\ActiveQuery 對(duì)象,該對(duì)象允許你通過 yii\db\ActiveQuery 方法定制查詢。

如上聲明后,執(zhí)行?$customer->bigOrders?就返回 總額大于100的訂單。使用以下代碼更改設(shè)定值:

$orders = $customer->getBigOrders(200)->all();

注意:關(guān)聯(lián)查詢返回的是 yii\db\ActiveQuery 的實(shí)例,如果像特性(如類屬性)那樣連接關(guān)聯(lián)數(shù)據(jù), 返回的結(jié)果是關(guān)聯(lián)查詢的結(jié)果,即 yii\db\ActiveRecord 的實(shí)例, 或者是數(shù)組,或者是 null ,取決于關(guān)聯(lián)關(guān)系的多樣性。如,$customer->getOrders()?返回ActiveQuery?實(shí)例,而?$customer->orders?返回Order?對(duì)象數(shù)組 (如果查詢結(jié)果為空則返回空數(shù)組)。

中間關(guān)聯(lián)表

有時(shí),兩個(gè)表通過中間表關(guān)聯(lián),定義這樣的關(guān)聯(lián)關(guān)系, 可以通過調(diào)用 yii\db\ActiveQuery::via() 方法或 yii\db\ActiveQuery::viaTable() 方法來定制 yii\db\ActiveQuery 對(duì)象 。

舉例而言,如果?order?表和?item?表通過中間表?order_item?關(guān)聯(lián)起來, 可以在?Order?類聲明?items?關(guān)聯(lián)關(guān)系取代中間表:

class Order extends \yii\db\ActiveRecord
{
    public function getItems()
    {
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->viaTable('order_item', ['order_id' => 'id']);
    }
}

兩個(gè)方法是相似的,除了 yii\db\ActiveQuery::via() 方法的第一個(gè)參數(shù)是使用 AR 類中定義的關(guān)聯(lián)名。 以上方法取代了中間表,等價(jià)于:

class Order extends \yii\db\ActiveRecord
{
    public function getOrderItems()
    {
        return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
    }

    public function getItems()
    {
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->via('orderItems');
    }
}

延遲加載和即時(shí)加載(又稱惰性加載與貪婪加載)

如前所述,當(dāng)你第一次連接關(guān)聯(lián)對(duì)象時(shí), AR 將執(zhí)行一個(gè)數(shù)據(jù)庫查詢 來檢索請(qǐng)求數(shù)據(jù)并填充到關(guān)聯(lián)對(duì)象的相應(yīng)屬性。 如果再次連接相同的關(guān)聯(lián)對(duì)象,不再執(zhí)行任何查詢語句,這種數(shù)據(jù)庫查詢的執(zhí)行方法稱為“延遲加載”。如:

// SQL executed: SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// SQL executed: SELECT * FROM order WHERE customer_id=1
$orders = $customer->orders;
// 沒有 SQL 語句被執(zhí)行
$orders2 = $customer->orders; //取回上次查詢的緩存數(shù)據(jù)

延遲加載非常實(shí)用,但是,在以下場(chǎng)景中使用延遲加載會(huì)遭遇性能問題:

// SQL executed: SELECT * FROM customer LIMIT 100
$customers = Customer::find()->limit(100)->all();

foreach ($customers as $customer) {
    // SQL executed: SELECT * FROM order WHERE customer_id=...
    $orders = $customer->orders;
    // ...處理 $orders...
}

假設(shè)數(shù)據(jù)庫查出的客戶超過100個(gè),以上代碼將執(zhí)行多少條 SQL 語句? 101 條!第一條 SQL 查詢語句取回100個(gè)客戶,然后, 每個(gè)客戶要執(zhí)行一條 SQL 查詢語句以取回該客戶的所有訂單。

為解決以上性能問題,可以通過調(diào)用 yii\db\ActiveQuery::with() 方法使用即時(shí)加載解決。

// SQL executed: SELECT * FROM customer LIMIT 100;
//               SELECT * FROM orders WHERE customer_id IN (1,2,...)
$customers = Customer::find()->limit(100)
    ->with('orders')->all();

foreach ($customers as $customer) {
    // 沒有 SQL 語句被執(zhí)行
    $orders = $customer->orders;
    // ...處理 $orders...
}

如你所見,同樣的任務(wù)只需要兩個(gè) SQL 語句。 >須知:通常,即時(shí)加載 N 個(gè)關(guān)聯(lián)關(guān)系而通過 via() 或者 viaTable() 定義了 M 個(gè)關(guān)聯(lián)關(guān)系, 將有 1+M+N 條 SQL 查詢語句被執(zhí)行:一個(gè)查詢?nèi)』刂鞅硇袛?shù), 一個(gè)查詢給每一個(gè) (M) 中間表,一個(gè)查詢給每個(gè) (N) 關(guān)聯(lián)表。 注意:當(dāng)用即時(shí)加載定制 select() 時(shí),確保連接 到關(guān)聯(lián)模型的列都被包括了,否則,關(guān)聯(lián)模型不會(huì)載入。如:

$orders = Order::find()->select(['id', 'amount'])->with('customer')->all();
// $orders[0]->customer 總是空的,使用以下代碼解決這個(gè)問題:
$orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all();

有時(shí)候,你想自由的自定義關(guān)聯(lián)查詢,延遲加載和即時(shí)加載都可以實(shí)現(xiàn),如:

$customer = Customer::findOne(1);
// 延遲加載: SELECT * FROM order WHERE customer_id=1 AND subtotal>100
$orders = $customer->getOrders()->where('subtotal>100')->all();

// 即時(shí)加載: SELECT * FROM customer LIMIT 100
//          SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100
$customers = Customer::find()->limit(100)->with([
    'orders' => function($query) {
        $query->andWhere('subtotal>100');
    },
])->all();

逆關(guān)系

關(guān)聯(lián)關(guān)系通常成對(duì)定義,如:Customer 可以有個(gè)名為 orders 關(guān)聯(lián)項(xiàng), 而 Order 也有個(gè)名為customer 的關(guān)聯(lián)項(xiàng):

class Customer extends ActiveRecord
{
    ....
    public function getOrders()
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id']);
    }
}

class Order extends ActiveRecord
{
    ....
    public function getCustomer()
    {
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
    }
}

如果我們執(zhí)行以下查詢,可以發(fā)現(xiàn)訂單的 customer 和 找到這些訂單的客戶對(duì)象并不是同一個(gè)。連接 customer->orders 將觸發(fā)一條 SQL 語句 而連接一個(gè)訂單的 customer 將觸發(fā)另一條 SQL 語句。

// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// 輸出 "不相同"
// SELECT * FROM order WHERE customer_id=1
// SELECT * FROM customer WHERE id=1
if ($customer->orders[0]->customer === $customer) {
    echo '相同';
} else {
    echo '不相同';
}

為避免多余執(zhí)行的后一條語句,我們可以為 customer或 orders 關(guān)聯(lián)關(guān)系定義相反的關(guān)聯(lián)關(guān)系,通過調(diào)用 yii\db\ActiveQuery::inverseOf() 方法可以實(shí)現(xiàn)。

class Customer extends ActiveRecord
{
    ....
    public function getOrders()
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');
    }
}

現(xiàn)在我們同樣執(zhí)行上面的查詢,我們將得到:

// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// 輸出相同
// SELECT * FROM order WHERE customer_id=1
if ($customer->orders[0]->customer === $customer) {
    echo '相同';
} else {
    echo '不相同';
}

以上我們展示了如何在延遲加載中使用相對(duì)關(guān)聯(lián)關(guān)系, 相對(duì)關(guān)系也可以用在即時(shí)加載中:

// SELECT * FROM customer
// SELECT * FROM order WHERE customer_id IN (1, 2, ...)
$customers = Customer::find()->with('orders')->all();
// 輸出相同
if ($customers[0]->orders[0]->customer === $customers[0]) {
    echo '相同';
} else {
    echo '不相同';
}

注意:相對(duì)關(guān)系不能在包含中間表的關(guān)聯(lián)關(guān)系中定義。 即是,如果你的關(guān)系是通過yii\db\ActiveQuery::via() 或 yii\db\ActiveQuery::viaTable()方法定義的, 就不能調(diào)用yii\db\ActiveQuery::inverseOf()方法了。

JOIN 類型關(guān)聯(lián)查詢

使用關(guān)系數(shù)據(jù)庫時(shí),普遍要做的是連接多個(gè)表并明確地運(yùn)用各種 JOIN 查詢。 JOIN SQL語句的查詢條件和參數(shù),使用 yii\db\ActiveQuery::joinWith() 可以重用已定義關(guān)系并調(diào)用 而不是使用 yii\db\ActiveQuery::join() 來實(shí)現(xiàn)目標(biāo)。

// 查找所有訂單并以客戶 ID 和訂單 ID 排序,并貪婪加載 "customer" 表
$orders = Order::find()->joinWith('customer')->orderBy('customer.id, order.id')->all();
// 查找包括書籍的所有訂單,并以 `INNER JOIN` 的連接方式即時(shí)加載 "books" 表
$orders = Order::find()->innerJoinWith('books')->all();

以上,方法 yii\db\ActiveQuery::innerJoinWith() 是訪問?INNER JOIN?類型的 yii\db\ActiveQuery::joinWith() 的快捷方式。

可以連接一個(gè)或多個(gè)關(guān)聯(lián)關(guān)系,可以自由使用查詢條件到關(guān)聯(lián)查詢, 也可以嵌套連接關(guān)聯(lián)查詢。如:

// 連接多重關(guān)系
// 找出24小時(shí)內(nèi)注冊(cè)客戶包含書籍的訂單
$orders = Order::find()->innerJoinWith([
    'books',
    'customer' => function ($query) {
        $query->where('customer.created_at > ' . (time() - 24 * 3600));
    }
])->all();
// 連接嵌套關(guān)系:連接 books 表及其 author 列
$orders = Order::find()->joinWith('books.author')->all();

代碼背后, Yii 先執(zhí)行一條 JOIN SQL 語句把滿足 JOIN SQL 語句查詢條件的主要模型查出, 然后為每個(gè)關(guān)系執(zhí)行一條查詢語句, bing填充相應(yīng)的關(guān)聯(lián)記錄。

yii\db\ActiveQuery::joinWith() 和 yii\db\ActiveQuery::with() 的區(qū)別是 前者連接主模型類和關(guān)聯(lián)模型類的數(shù)據(jù)表來檢索主模型, 而后者只查詢和檢索主模型類。 檢索主模型

由于這個(gè)區(qū)別,你可以應(yīng)用只針對(duì)一條 JOIN SQL 語句起效的查詢條件。 如,通過關(guān)聯(lián)模型的查詢條件過濾主模型,如前例, 可以使用關(guān)聯(lián)表的列來挑選主模型數(shù)據(jù),

當(dāng)使用 yii\db\ActiveQuery::joinWith() 方法時(shí)可以響應(yīng)沒有歧義的列名。 In the above examples, we use?item.id?and?order.id?to disambiguate the?id?column references 因?yàn)橛唵伪砗晚?xiàng)目表都包括?id?列。

當(dāng)連接關(guān)聯(lián)關(guān)系時(shí),關(guān)聯(lián)關(guān)系默認(rèn)使用即時(shí)加載。你可以 通過傳參數(shù)?$eagerLoading?來決定在指定關(guān)聯(lián)查詢中是否使用即時(shí)加載。

默認(rèn) yii\db\ActiveQuery::joinWith() 使用左連接來連接關(guān)聯(lián)表。 你也可以傳?$joinType?參數(shù)來定制連接類型。 你也可以使用 yii\db\ActiveQuery::innerJoinWith()。

以下是?INNER JOIN?的簡(jiǎn)短例子:

// 查找包括書籍的所有訂單,但 "books" 表不使用即時(shí)加載
$orders = Order::find()->innerJoinWith('books', false)->all();
// 等價(jià)于:
$orders = Order::find()->joinWith('books', false, 'INNER JOIN')->all();

有時(shí)連接兩個(gè)表時(shí),需要在關(guān)聯(lián)查詢的 ON 部分指定額外條件。 這可以通過調(diào)用 yii\db\ActiveQuery::onCondition() 方法實(shí)現(xiàn):

class User extends ActiveRecord
{
    public function getBooks()
    {
        return $this->hasMany(Item::className(), ['owner_id' => 'id'])->onCondition(['category_id' => 1]);
    }
}

在上面, yii\db\ActiveRecord::hasMany() 方法回傳了一個(gè) yii\db\ActiveQuery 對(duì)象, 當(dāng)你用 yii\db\ActiveQuery::joinWith() 執(zhí)行一條查詢時(shí),取決于正被調(diào)用的是哪個(gè) yii\db\ActiveQuery::onCondition(), 返回?category_id?為 1 的 items

當(dāng)你用 yii\db\ActiveQuery::joinWith() 進(jìn)行一次查詢時(shí),“on-condition”條件會(huì)被放置在相應(yīng)查詢語句的 ON 部分, 如:

// SELECT user.* FROM user LEFT JOIN item ON item.owner_id=user.id AND category_id=1
// SELECT * FROM item WHERE owner_id IN (...) AND category_id=1
$users = User::find()->joinWith('books')->all();

注意:如果通過 yii\db\ActiveQuery::with() 進(jìn)行貪婪加載或使用惰性加載的話,則 on 條件會(huì)被放置在對(duì)應(yīng) SQL語句的?WHERE?部分。 因?yàn)?,此時(shí)此處并沒有發(fā)生 JOIN 查詢。比如:

// SELECT * FROM user WHERE id=10
$user = User::findOne(10);
// SELECT * FROM item WHERE owner_id=10 AND category_id=1
$books = $user->books;

關(guān)聯(lián)表操作

AR 提供了下面兩個(gè)方法用來建立和解除兩個(gè)關(guān)聯(lián)對(duì)象之間的關(guān)系:

  • yii\db\ActiveRecord::link()
  • yii\db\ActiveRecord::unlink()

例如,給定一個(gè)customer和order對(duì)象,我們可以通過下面的代碼使得customer對(duì)象擁有order對(duì)象:

$customer = Customer::findOne(1);
$order = new Order();
$order->subtotal = 100;
$customer->link('orders', $order);

yii\db\ActiveRecord::link() 調(diào)用上述將設(shè)置 customer_id 的順序是 $customer 的主鍵值,然后調(diào)用 yii\db\ActiveRecord::save() 要將順序保存到數(shù)據(jù)庫中。

作用域

當(dāng)你調(diào)用yii\db\ActiveRecord::find() 或 yii\db\ActiveRecord::findBySql()方法時(shí),將會(huì)返回一個(gè)yii\db\ActiveQuery實(shí)例。之后,你可以調(diào)用其他查詢方法,如 yii\db\ActiveQuery::where(),yii\db\ActiveQuery::orderBy(), 進(jìn)一步的指定查詢條件。

有時(shí)候你可能需要在不同的地方使用相同的查詢方法。如果出現(xiàn)這種情況,你應(yīng)該考慮定義所謂的作用域。作用域是本質(zhì)上要求一組的查詢方法來修改查詢對(duì)象的自定義查詢類中定義的方法。 之后你就可以像使用普通方法一樣使用作用域。

只需兩步即可定義一個(gè)作用域。首先給你的model創(chuàng)建一個(gè)自定義的查詢類,在此類中定義的所需的范圍方法。例如,給Comment模型創(chuàng)建一個(gè) CommentQuery類,然后在CommentQuery類中定義一個(gè)active()的方法為作用域,像下面的代碼:

namespace app\models;

use yii\db\ActiveQuery;

class CommentQuery extends ActiveQuery
{
    public function active($state = true)
    {
        $this->andWhere(['active' => $state]);
        return $this;
    }
}

重點(diǎn):

  1. 類必須繼承 yii\db\ActiveQuery (或者是其他的 ActiveQuery ,比如 yii\mongodb\ActiveQuery)。
  2. 必須是一個(gè)public類型的方法且必須返回 $this 實(shí)現(xiàn)鏈?zhǔn)讲僮鳌?梢詡魅雲(yún)?shù)。
  3. 檢查 yii\db\ActiveQuery 對(duì)于修改查詢條件是非常有用的方法。

其次,覆蓋yii\db\ActiveRecord::find() 方法使其返回自定義的查詢對(duì)象而不是常規(guī)的yii\db\ActiveQuery。對(duì)于上述例子,你需要編寫如下代碼:

namespace app\models;

use yii\db\ActiveRecord;

class Comment extends ActiveRecord
{
    /**
     * @inheritdoc
     * @return CommentQuery
     */
    public static function find()
    {
        return new CommentQuery(get_called_class());
    }
}

就這樣,現(xiàn)在你可以使用自定義的作用域方法了:

$comments = Comment::find()->active()->all();
$inactiveComments = Comment::find()->active(false)->all();

你也能在定義的關(guān)聯(lián)里使用作用域方法,比如:

class Post extends \yii\db\ActiveRecord
{
    public function getActiveComments()
    {
        return $this->hasMany(Comment::className(), ['post_id' => 'id'])->active();

    }
}

或者在執(zhí)行關(guān)聯(lián)查詢的時(shí)候使用(on-the-fly 是啥?):

$posts = Post::find()->with([
    'comments' => function($q) {
        $q->active();
    }
])->all();

默認(rèn)作用域

如果你之前用過 Yii 1.1 就應(yīng)該知道默認(rèn)作用域的概念。一個(gè)默認(rèn)的作用域可以作用于所有查詢。你可以很容易的通過重寫yii\db\ActiveRecord::find()方法來定義一個(gè)默認(rèn)作用域,例如:

public static function find()
{
    return parent::find()->where(['deleted' => false]);
}

注意,你之后所有的查詢都不能用 yii\db\ActiveQuery::where(),但是可以用 yii\db\ActiveQuery::andWhere() 和 yii\db\ActiveQuery::orWhere(),他們不會(huì)覆蓋掉默認(rèn)作用域。(譯注:如果你要使用默認(rèn)作用域,就不能在 xxx::find()后使用where()方法,你必須使用andXXX()或者orXXX()系的方法,否則默認(rèn)作用域不會(huì)起效果,至于原因,打開where()方法的代碼一看便知)

事務(wù)操作

當(dāng)執(zhí)行幾個(gè)相關(guān)聯(lián)的數(shù)據(jù)庫操作的時(shí)候

TODO: FIXME: WIP, TBD,?https://github.com/yiisoft/yii2/issues/226

, yii\db\ActiveRecord::afterSave(), yii\db\ActiveRecord::beforeDelete() and/or yii\db\ActiveRecord::afterDelete() 生命周期周期方法(life cycle methods 我覺得這句翻譯成“模板方法”會(huì)不會(huì)更好點(diǎn)?)。開發(fā)者可以通過重寫yii\db\ActiveRecord::save()方法然后在控制器里使用事務(wù)操作,嚴(yán)格地說是似乎不是一個(gè)好的做法 (召回"瘦控制器 / 肥模型"基本規(guī)則)。

這些方法在這里(如果你不明白自己實(shí)際在干什么,請(qǐng)不要使用他們),Models:

class Feature extends \yii\db\ActiveRecord
{
    // ...

    public function getProduct()
    {
        return $this->hasOne(Product::className(), ['id' => 'product_id']);
    }
}

class Product extends \yii\db\ActiveRecord
{
    // ...

    public function getFeatures()
    {
        return $this->hasMany(Feature::className(), ['product_id' => 'id']);
    }
}

重寫 yii\db\ActiveRecord::save() 方法:

class ProductController extends \yii\web\Controller
{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

(譯注:我覺得上面應(yīng)該是原手冊(cè)里的bug)

在控制器層使用事務(wù):

class ProductController extends \yii\web\Controller
{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

作為這些脆弱方法的替代,你應(yīng)該使用原子操作方案特性。

class Feature extends \yii\db\ActiveRecord
{
    // ...

    public function getProduct()
    {
        return $this->hasOne(Product::className(), ['product_id' => 'id']);
    }

    public function scenarios()
    {
        return [
            'userCreates' => [
                'attributes' => ['name', 'value'],
                'atomic' => [self::OP_INSERT],
            ],
        ];
    }
}

class Product extends \yii\db\ActiveRecord
{
    // ...

    public function getFeatures()
    {
        return $this->hasMany(Feature::className(), ['id' => 'product_id']);
    }

    public function scenarios()
    {
        return [
            'userCreates' => [
                'attributes' => ['title', 'price'],
                'atomic' => [self::OP_INSERT],
            ],
        ];
    }

    public function afterValidate()
    {
        parent::afterValidate();
        // FIXME: TODO: WIP, TBD
    }

    public function afterSave($insert)
    {
        parent::afterSave($insert);
        if ($this->getScenario() === 'userCreates') {
            // FIXME: TODO: WIP, TBD
        }
    }
}

Controller里的代碼將變得很簡(jiǎn)潔:

class ProductController extends \yii\web\Controller
{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

控制器非常簡(jiǎn)潔:

class ProductController extends \yii\web\Controller
{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

樂觀鎖(Optimistic Locks)

TODO

被污染屬性

當(dāng)你調(diào)用yii\db\ActiveRecord::save()用于保存活動(dòng)記錄(Active Record)實(shí)例時(shí),只有被污染的屬性才會(huì)被保存。一個(gè)屬性是否認(rèn)定為被污染取決于它的值自從最后一次從數(shù)據(jù)庫加載或者最近一次保存到數(shù)據(jù)庫后到現(xiàn)在是否被修改過。注意:無論活動(dòng)記錄(Active Record)是否有被污染屬性,數(shù)據(jù)驗(yàn)證始終會(huì)執(zhí)行。

活動(dòng)記錄(Active Record)會(huì)自動(dòng)維護(hù)一個(gè)污染數(shù)據(jù)列表。它的工作方式是通過維護(hù)一個(gè)較舊屬性值版本,并且將它們與最新的進(jìn)行比較。你可以通過調(diào)用yii\db\ActiveRecord::getDirtyAttributes()來獲取當(dāng)前的污染屬性。你也可以調(diào)用yii\db\ActiveRecord::markAttributeDirty()來顯示的標(biāo)記一個(gè)屬性為污染屬性。

如果你對(duì)最近一次修改前的屬性值感興趣,你可以調(diào)用yii\db\ActiveRecord::getOldAttributes() 或 yii\db\ActiveRecord::getOldAttribute()。

另見

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)