Yii2.0——數據庫訪問(DAO)

執行 SQL 查詢

一旦擁有了 DB Connection 實例, 可以按照下列步驟來執行 SQL 查詢:

使用純SQL查詢來創建出 yii\db\Command;

綁定參數 (可選的);

調用 yii\db\Command 里 SQL 執行方法中的一個。

下列例子展示了幾種不同的從數據庫取得數據的方法:

// 返回多行. 每行都是列名和值的關聯數組.

// 如果該查詢沒有結果則返回空數組

$posts = Yii::$app->db->createCommand('SELECT * FROM post')

->queryAll();

// 返回一行 (第一行)

// 如果該查詢沒有結果則返回 false

$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=1')

->queryOne();

// 返回一列 (第一列)

// 如果該查詢沒有結果則返回空數組

$titles = Yii::$app->db->createCommand('SELECT title FROM post')

->queryColumn();

// 返回一個標量值

// 如果該查詢沒有結果則返回 false

$count = Yii::$app->db->createCommand('SELECT COUNT(*) FROM post')

->queryScalar();

注意: 為了保持精度, 即使對應的數據庫列類型為數值型, 所有從數據庫取得的數據都被表現為字符串。

綁定參數

當使用帶參數的 SQL 來創建數據庫命令時, 幾乎總是應該使用綁定參數的方法來防止 SQL 注入攻擊,例如:

$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status')

->bindValue(':id', $_GET['id'])

->bindValue(':status', 1)

->queryOne();

在 SQL 語句中, 可以嵌入一個或多個參數占位符(例如,上述例子中的 :id )。 一個參數占位符應該是以冒號開頭的字符串。 之后可以調用下面綁定參數的方法來綁定參數值:

bindValue():綁定一個參數值

bindValues():在一次調用中綁定多個參數值

bindParam():與 bindValue() 相似,但是也支持綁定參數引用。

下面的例子展示了幾個可供選擇的綁定參數的方法:

$params = [':id' => $_GET['id'], ':status' => 1];

$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status')

->bindValues($params)

->queryOne();

$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status', $params)

->queryOne();

綁定參數是通過 預處理語句 實現的。 除了防止 SQL 注入攻擊, 它也可以通過一次預處理 SQL 語句, 使用不同參數多次執行, 來提升性能。 例如:

$command = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id');

$post1 = $command->bindValue(':id', 1)->queryOne();

$post2 = $command->bindValue(':id', 2)->queryOne();

// ...

因為 bindParam() 支持通過引用來綁定參數, 上述代碼也可以像下面這樣寫:

$command = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id')

->bindParam(':id', $id);

$id = 1;

$post1 = $command->queryOne();

$id = 2;

$post2 = $command->queryOne();

// ...

請注意,在執行語句前將占位符綁定到 $id 變量, 然后在之后的每次執行前改變變量的值(這通常是用循環來完成的)。 以這種方式執行查詢比為每個不同的參數值執行一次新的查詢要高效得多得多。

執行非查詢語句

上面部分中介紹的 queryXyz() 方法都處理的是從數據庫返回數據的查詢語句。 對于那些不取回數據的語句, 應該調用的是 yii\db\Command::execute() 方法。 例如,

Yii::$app->db->createCommand('UPDATE post SET status=1 WHERE id=1')

->execute();

yii\db\Command::execute() 方法返回執行 SQL 所影響到的行數。

對于 INSERT, UPDATE 和 DELETE 語句, 不再需要寫純SQL語句了, 可以直接調用 insert()、 update()、 delete(), 來構建相應的 SQL 語句。 這些方法將正確地引用表和列名稱以及綁定參數值。 例如,

// INSERT (table name, column values)

Yii::$app->db->createCommand()->insert('user', [

'name' => 'Sam',

'age' => 30,

])->execute();

// UPDATE (table name, column values, condition)

Yii::$app->db->createCommand()->update('user', ['status' => 1], 'age > 30')->execute();

// DELETE (table name, condition)

Yii::$app->db->createCommand()->delete('user', 'status = 0')->execute();

也可以調用 batchInsert() 來一次插入多行, 這比一次插入一行要高效得多:

// table name, column names, column values

Yii::$app->db->createCommand()->batchInsert('user', ['name', 'age'], [

['Tom', 30],

['Jane', 20],

['Linda', 25],

])->execute();

上述的方法只是構建出語句, 總是需要調用 execute() 來真正地執行它們。

引用表和列名稱

當寫與數據庫無關的代碼時, 正確地引用表和列名稱總是一件頭疼的事, 因為不同的數據庫有不同的名稱引用規則, 為了克服這個問題, 可以使用下面由 Yii 提出的引用語法。

[[column name]]: 使用兩對方括號來將列名括起來;

{{table name}}: 使用兩對大括號來將表名括起來。

Yii DAO 將自動地根據數據庫的具體語法來將這些結構轉化為對應的被引用的列或者表名稱。 例如,

// 在 MySQL 中執行該 SQL : SELECT COUNT(`id`) FROM `employee`

$count = Yii::$app->db->createCommand("SELECT COUNT([[id]]) FROM {{employee}}")

->queryScalar();

使用表前綴

如果的數據庫表名大多都擁有一個共同的前綴,可以使用 Yii DAO 所提供的表前綴功能。

首先,通過應用配置中的 yii\db\Connection::$tablePrefix 屬性來指定表前綴:

return [

// ...

'components' => [

// ...

'db' => [

// ...

'tablePrefix' => 'tbl_',

],

],

];

接著在的代碼中, 當需要涉及到一張表名中包含該前綴的表時, 應使用語法 {{%table_name}}。 百分號將被自動地替換為在配置 DB 組件時指定的表前綴。 例如,

// 在 MySQL 中執行該 SQL: SELECT COUNT(`id`) FROM `tbl_employee`

$count = Yii::$app->db->createCommand("SELECT COUNT([[id]]) FROM {{%employee}}")

->queryScalar();

執行事務

當順序地執行多個相關的語句時, 或許需要將它們包在一個事務中來保證數據庫的完整性和一致性。 如果這些語句中的任何一個失敗了, 數據庫將回滾到這些語句執行前的狀態。

下面的代碼展示了一個使用事務的典型方法:

Yii::$app->db->transaction(function($db) {

$db->createCommand($sql1)->execute();

$db->createCommand($sql2)->execute();

// ... executing other SQL statements ...

});

上述代碼等價于下面的代碼, 但是下面的代碼給予了對于錯誤處理代碼的更多掌控:

$db = Yii::$app->db;

$transaction = $db->beginTransaction();

try {

$db->createCommand($sql1)->execute();

$db->createCommand($sql2)->execute();

// ... executing other SQL statements ...

$transaction->commit();

} catch(\Exception $e) {

$transaction->rollBack();

throw $e;

}

通過調用 beginTransaction() 方法, 一個新事務開始了。 事務被表示為一個存儲在 $transaction 變量中的 yii\db\Transaction 對象。 然后,被執行的語句都被包含在一個 try...catch... 塊中。 如果所有的語句都被成功地執行了, commit() 將被調用來提交這個事務。 否則, 如果異常被觸發并被捕獲, rollBack() 方法將被調用, 來回滾事務中失敗語句之前所有語句所造成的改變。 throw $e 將重新拋出該異常, 就好像我們沒有捕獲它一樣, 因此正常的錯誤處理程序將處理它。

指定隔離級別

Yii 也支持事務設置隔離級別。 默認情況下, 當開啟一個新事務, 它將使用數據庫所設定的隔離級別。 也可以向下面這樣重載默認的隔離級別,

$isolationLevel = \yii\db\Transaction::REPEATABLE_READ;

Yii::$app->db->transaction(function ($db) {

....

}, $isolationLevel);

// or alternatively

$transaction = Yii::$app->db->beginTransaction($isolationLevel);

Yii 為四個最常用的隔離級別提供了常量:

yii\db\Transaction::READ_UNCOMMITTED - 最弱的隔離級別,臟讀、不可重復讀以及幻讀都可能發生。

yii\db\Transaction::READ_COMMITTED - 避免了臟讀。

yii\db\Transaction::REPEATABLE_READ - 避免了臟讀和不可重復讀。

yii\db\Transaction::SERIALIZABLE - 最強的隔離級別, 避免了上述所有的問題。

除了使用上述的常量來指定隔離級別, 還可以使用數據庫所支持的具有有效語法的字符串。 比如,在 PostgreSQL 中, 可以使用 SERIALIZABLE READ ONLY DEFERRABLE。

請注意,一些數據庫只允許為整個連接設置隔離級別, 即使之后什么也沒指定, 后來的事務都將獲得與之前相同的隔離級別。 使用此功能時,需要為所有的事務明確地設置隔離級別來避免沖突的設置。 在本文寫作之時, 只有 MSSQL 和 SQLite 受這些限制的影響。

注意: SQLite 只支持兩種隔離級別, 所以只能使用 READ UNCOMMITTED 和 SERIALIZABLE。 使用其他級別將導致異常的拋出。

注意: PostgreSQL 不支持在事務開啟前設定隔離級別, 因此,不能在開啟事務時直接指定隔離級別。 必須在事務開始后再調用 yii\db\Transaction::setIsolationLevel()。

嵌套事務

如果數據庫支持保存點, 可以像下面這樣嵌套多個事務:

Yii::$app->db->transaction(function ($db) {

// outer transaction

$db->transaction(function ($db) {

// inner transaction

});

});

或者,

$db = Yii::$app->db;

$outerTransaction = $db->beginTransaction();

try {

$db->createCommand($sql1)->execute();

$innerTransaction = $db->beginTransaction();

try {

$db->createCommand($sql2)->execute();

$innerTransaction->commit();

} catch (\Exception $e) {

$innerTransaction->rollBack();

throw $e;

}

$outerTransaction->commit();

} catch (\Exception $e) {

$outerTransaction->rollBack();

throw $e;

}

復制和讀寫分離

許多數據庫支持數據庫復制來獲得更好的數據庫可用性, 以及更快的服務器響應時間。 通過數據庫復制功能, 數據從所謂的主服務器被復制到從服務器。 所有的寫和更新必須發生在主服務器上, 而讀可以發生在從服務器上。

為了利用數據庫復制并且完成讀寫分離,可以按照下面的方法來配置 yii\db\Connection 組件:

[

'class' => 'yii\db\Connection',

// 主庫的配置

'dsn' => 'dsn for master server',

'username' => 'master',

'password' => '',

// 從庫的通用配置

'slaveConfig' => [

'username' => 'slave',

'password' => '',

'attributes' => [

// 使用一個更小的連接超時

PDO::ATTR_TIMEOUT => 10,

],

],

// 從庫的配置列表

'slaves' => [

['dsn' => 'dsn for slave server 1'],

['dsn' => 'dsn for slave server 2'],

['dsn' => 'dsn for slave server 3'],

['dsn' => 'dsn for slave server 4'],

],

]

上述的配置指定了一主多從的設置。 這些從庫其中之一將被建立起連接并執行讀操作, 而主庫將被用來執行寫操作。 這樣的讀寫分離將通過上述配置自動地完成。 比如,

// 使用上述配置來創建一個 Connection 實例

Yii::$app->db = Yii::createObject($config);

// 在從庫中的一個上執行語句

$rows = Yii::$app->db->createCommand('SELECT * FROM user LIMIT 10')->queryAll();

// 在主庫上執行語句

Yii::$app->db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute();

Info: 通過調用 yii\db\Command::execute() 來執行的語句都被視為寫操作, 而其他所有通過調用 yii\db\Command 中任一 "query" 方法來執行的語句都被視為讀操作。 可以通過 Yii::$app->db->slave 來獲取當前有效的從庫連接。

Connection 組件支持從庫間的負載均衡和失效備援, 當第一次執行讀操作時, Connection 組件將隨機地挑選出一個從庫并嘗試與之建立連接, 如果這個從庫被發現為”掛掉的“, 將嘗試連接另一個從庫。 如果沒有一個從庫是連接得上的, 那么將試著連接到主庫上。 通過配置 server status cache, 一個“掛掉的”服務器將會被記住, 因此,在一個 yii\db\Connection::serverRetryInterval 內將不再試著連接該服務器。

Info: 在上面的配置中, 每個從庫都共同地指定了 10 秒的連接超時時間,如果一個從庫在 10 秒內不能被連接上, 它將被視為“掛掉的”。 可以根據實際環境來調整該參數。

也可以配置多主多從。例如,

[

'class' => 'yii\db\Connection',

// 主庫通用的配置

'masterConfig' => [

'username' => 'master',

'password' => '',

'attributes' => [

// use a smaller connection timeout

PDO::ATTR_TIMEOUT => 10,

],

],

// 主庫配置列表

'masters' => [

['dsn' => 'dsn for master server 1'],

['dsn' => 'dsn for master server 2'],

],

// 從庫的通用配置

'slaveConfig' => [

'username' => 'slave',

'password' => '',

'attributes' => [

// use a smaller connection timeout

PDO::ATTR_TIMEOUT => 10,

],

],

// 從庫配置列表

'slaves' => [

['dsn' => 'dsn for slave server 1'],

['dsn' => 'dsn for slave server 2'],

['dsn' => 'dsn for slave server 3'],

['dsn' => 'dsn for slave server 4'],

],

]

上述配置指定了兩個主庫和兩個從庫。 Connection 組件在主庫之間, 也支持如從庫間般的負載均衡和失效備援。 唯一的差別是, 如果沒有主庫可用,將拋出一個異常。

注意: 當使用 masters 屬性來配置一個或多個主庫時, 所有其他指定數據庫連接的屬性 (例如 dsn, username, password) 與 Connection 對象本身將被忽略。

默認情況下, 事務使用主庫連接, 一個事務內, 所有的數據庫操作都將使用主庫連接, 例如,

$db = Yii::$app->db;

// 在主庫上啟動事務

$transaction = $db->beginTransaction();

try {

// 兩個語句都是在主庫上執行的

$rows = $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll();

$db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute();

$transaction->commit();

} catch(\Exception $e) {

$transaction->rollBack();

throw $e;

}

如果想在從庫上開啟事務,應該明確地像下面這樣做:

$transaction = Yii::$app->db->slave->beginTransaction();

有時,或許想要強制使用主庫來執行讀查詢。 這可以通過 useMaster() 方法來完成:

$rows = Yii::$app->db->useMaster(function ($db) {

return $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll();

});

也可以明確地將 `Yii::$app->db->enableSlaves` 設置為 false 來將所有的讀操作指向主庫連接。

操縱數據庫模式

Yii DAO 提供了一套完整的方法來操縱數據庫模式, 如創建表、從表中刪除一列,等等。這些方法羅列如下:

createTable():創建一張表

renameTable():重命名一張表

dropTable():刪除一張表

truncateTable():刪除一張表中的所有行

addColumn():增加一列

renameColumn():重命名一列

dropColumn():刪除一列

alterColumn():修改一列

addPrimaryKey():增加主鍵

dropPrimaryKey():刪除主鍵

addForeignKey():增加一個外鍵

dropForeignKey():刪除一個外鍵

createIndex():增加一個索引

dropIndex():刪除一個索引

這些方法可以如下地使用:

// CREATE TABLE

Yii::$app->db->createCommand()->createTable('post', [

'id' => 'pk',

'title' => 'string',

'text' => 'text',

]);

上面的數組描述要創建的列的名稱和類型。 對于列的類型, Yii 提供了一套抽象數據類型來允許定義出數據庫無關的模式。 這些將根據表所在數據庫的種類, 被轉換為特定的類型定義。

除了改變數據庫模式, 也可以通過 DB Connection 的 getTableSchema() 方法來檢索某張表的定義信息。 例如,

$table = Yii::$app->db->getTableSchema('post');

該方法返回一個 yii\db\TableSchema 對象, 它包含了表中的列、主鍵、外鍵,等等的信息。 所有的這些信息主要被 query builder 和 active record 所使用,來幫助寫出數據庫無關的代碼。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容