本章我們首先從ThinkPHP5.0
的數(shù)據(jù)庫訪問層架構(gòu)設(shè)計原理開始,然后熟悉下數(shù)據(jù)庫的配置慕购,并掌握如何進行基礎(chǔ)的查詢操作蜡吧,并簡單介紹了分布式、存儲過程及事務(wù)征绎,學(xué)習(xí)內(nèi)容主要包括:
- 數(shù)據(jù)庫架構(gòu)設(shè)計
- 數(shù)據(jù)庫配置
- 如何開始查詢
- 使用參數(shù)綁定
- 查詢返回值
- 動態(tài)連接數(shù)據(jù)庫
- 分布式支持
- 存儲過程調(diào)用
- 數(shù)據(jù)庫事務(wù)
- 總結(jié)
數(shù)據(jù)庫架構(gòu)設(shè)計
使用框架開發(fā)應(yīng)用蹲姐,一般不需要直接操作數(shù)據(jù)庫,而是通過框架封裝好的數(shù)據(jù)庫中間層對數(shù)據(jù)庫進行操作人柿。這樣的好處主要有兩個:一是簡化數(shù)據(jù)庫操作柴墩,二是做到跨數(shù)據(jù)庫的一致性。這種設(shè)計的中間層通常稱之為數(shù)據(jù)庫訪問抽象層凫岖,簡稱數(shù)據(jù)訪問層(DAL
)江咳,ThinkPHP5的數(shù)據(jù)訪問層是基于PHP內(nèi)置的PDO
對象實現(xiàn)。一般抽象層本身并不直接操作數(shù)據(jù)庫隘截,而是通過驅(qū)動來實現(xiàn)具體的數(shù)據(jù)庫操作扎阶。
ThinkPHP5.0
的數(shù)據(jù)庫設(shè)計相比之前版本更加合理汹胃,數(shù)據(jù)訪問層劃分的更細化婶芭,把數(shù)據(jù)訪問對象分成了連接器、查詢器着饥、生成器等多個對象犀农,并通過數(shù)據(jù)庫訪問入口類統(tǒng)一調(diào)用,分工更明確宰掉,各司其職呵哨,欲知詳情且聽我慢慢道來。
ThinkPHP數(shù)據(jù)訪問層設(shè)計示意圖:
5.1版本的架構(gòu)略微進行了一些調(diào)整轨奄,變成:
數(shù)據(jù)庫入口類Db
平常我們的數(shù)據(jù)庫操作使用的類庫一般都是數(shù)據(jù)庫的入口類think\Db
孟害。這個類非常的簡單,主要就是一個connect
方法挪拟,根據(jù)數(shù)據(jù)庫配置參數(shù)連接數(shù)據(jù)庫(注意這里的連接并非真正的連接數(shù)據(jù)庫挨务,只是做好了隨時連接的準(zhǔn)備工作,只有在實際查詢的時候才會真正去連接數(shù)據(jù)庫玉组,是一種惰性連接)并獲取到數(shù)據(jù)庫連接對象的實例谎柄。
Db
類都是靜態(tài)方法調(diào)用,但看起來這個類啥都沒實現(xiàn)惯雳,那是怎么操作數(shù)據(jù)庫的呢朝巫,其實就是封裝了數(shù)據(jù)庫操作方法的靜態(tài)調(diào)用(利用__callStatic
方法),下面是代碼實現(xiàn):
// 調(diào)用驅(qū)動類的方法
public static function __callStatic($method, $params)
{
// 自動初始化數(shù)據(jù)庫
return call_user_func_array([self::connect(), $method], $params);
}
理論上來說石景,框架并不依賴Db
類劈猿,該類的存在只是為了簡化數(shù)據(jù)庫抽象層的操作而提供的一個工廠類拙吉,否則你就需要單獨實例化不同的數(shù)據(jù)庫連接類。因此糙臼,看似可有可無的Db
類就成了數(shù)據(jù)訪問層實現(xiàn)的點睛之筆了庐镐。
所有的數(shù)據(jù)庫操作都是經(jīng)過
Db
類調(diào)用,并且Db
類是一個靜態(tài)類变逃,但Db
類自身只有一個公共方法connect
必逆。
連接器類Connection
顧名思義,連接類的作用就是連接數(shù)據(jù)庫揽乱,也稱為連接器名眉。我們知道,不同的數(shù)據(jù)庫的連接方式和參數(shù)都是不同的凰棉,連接類就是要解決這個差異問題损拢。
數(shù)據(jù)庫入口類里面實例化的類其實就是對應(yīng)數(shù)據(jù)庫的連接類,連接類的基類是think\db\Connection
撒犀。例如福压,需要連接Mysql
數(shù)據(jù)庫的話,就必須定義一個Mysql
連接類(內(nèi)置由think\db\connector\Mysql
類實現(xiàn)或舞,繼承了think\db\Connection
類)荆姆,當(dāng)然具體的連接類名沒有固定的規(guī)范(例如,MongoDb
的連接類就是think\mongo\Connection
)映凳。如果某個數(shù)據(jù)庫的連接擴展類沒有繼承think\db\Connection
胆筒,那就意味著所有的數(shù)據(jù)庫底層操作有可能被接管,在個別特殊的數(shù)據(jù)庫的擴展中就有類似的實現(xiàn)诈豌,例如MongoDb
數(shù)據(jù)庫擴展仆救。
數(shù)據(jù)庫連接都是惰性的,只有最終執(zhí)行SQL的時候才會進行連接矫渔。
連接器是數(shù)據(jù)訪問層的基礎(chǔ)彤蔽,基于PHP本身的PDO
實現(xiàn)(如果你還不了解PDO
,請參考PHP官方手冊中PDO部分庙洼,不在本書的討論范疇)顿痪,連接類的主要作用就是連接具體的數(shù)據(jù)庫,以及完成基本的數(shù)據(jù)庫底層操作送膳,包括對分布式员魏、存儲過程和事務(wù)的完善處理。而更多的數(shù)據(jù)操作則交由查詢類完成叠聋。
框架內(nèi)置的連接類包括:
數(shù)據(jù)庫 | 連接類 |
---|---|
Mysql | think\db\connector\Mysql |
Pgsql | think\db\connector\Pgsql |
Sqlite | think\db\connector\Sqlite |
Sqlsrv | think\db\connector\Sqlsrv |
如果是僅僅使用原生SQL查詢的話撕阎,只需要使用連接類就可以了(通過調(diào)用Db類完成)
連接器類的作用小結(jié):
- 連接數(shù)據(jù)庫;
- 獲取數(shù)據(jù)表和字段信息碌补;
- 基礎(chǔ)查詢(原生查詢);
- 事務(wù)支持;
- 分布式支持求橄;
查詢器類Query
除了基礎(chǔ)的原生查詢可以在連接類完成之外,其它的查詢操作都是調(diào)用查詢類的方法照藻,查詢類內(nèi)完成了數(shù)據(jù)訪問層最重要的工作,銜接了連接類和生成類汗侵,統(tǒng)一了數(shù)據(jù)庫的查詢用法幸缕,所以查詢類是不需要單獨驅(qū)動配合的,我們也稱之為查詢器晰韵。無論采用什么數(shù)據(jù)庫发乔,我們的查詢方式是統(tǒng)一的,因為數(shù)據(jù)訪問層核心只有一個唯一的查詢類:think\db\Query
雪猪。
Query
類封裝了所有的數(shù)據(jù)庫CURD
方法的優(yōu)雅實現(xiàn)栏尚,包括鏈?zhǔn)椒椒案鞣N查詢,并自動使用了PDO
參數(shù)綁定(參數(shù)自動綁定是在生成器類解析生成SQL時完成)只恨,最大程度地保護你的程序避免受數(shù)據(jù)庫注入攻擊译仗,查詢操作會調(diào)用生成類生成對應(yīng)數(shù)據(jù)庫的SQL語句,然后再調(diào)用連接類提供的底層原生查詢方法執(zhí)行最終的數(shù)據(jù)庫查詢操作官觅。
所有的數(shù)據(jù)庫查詢都使用了
PDO
的預(yù)處理和參數(shù)綁定機制纵菌。你所看到的大部分?jǐn)?shù)據(jù)庫方法都來自于查詢類而并非Db
類,這一點很關(guān)鍵缰猴,也就是說雖然我們始終使用Db
類操作數(shù)據(jù)庫产艾,而實際上大部分方法都是由查詢器類提供的方法疤剑。
生成器類Builder
生成類的作用是接收Query
類的所有查詢參數(shù)滑绒,并負責(zé)解析生成對應(yīng)數(shù)據(jù)庫的原生SQL
語法,然后返回給Query
類進行后續(xù)的處理(包括交給連接類進行SQL
執(zhí)行和返回結(jié)果處理)隘膘,也稱為(語法)生成器疑故。生成類的作用其實就是解決不同的數(shù)據(jù)庫查詢語法之間的差異。查詢類實現(xiàn)了統(tǒng)一的查詢接口弯菊,而生成類負責(zé)數(shù)據(jù)庫底層的查詢對接纵势。
生成類一般不需要自己調(diào)用,而是由查詢類自動調(diào)用的管钳。也可以這么理解钦铁,生成類和查詢類是一體的,事實上它們合起來就是通常我們所說的查詢構(gòu)造器(因為實際的查詢操作還是在連接器中執(zhí)行的)才漆。
通常每一個數(shù)據(jù)庫連接類都會對應(yīng)一個生成類牛曹,框架內(nèi)置的生成類包括:
數(shù)據(jù)庫 | 生成類 |
---|---|
Mysql | think\db\builder\Mysql |
Pgsql | think\db\builder\Pgsql |
Sqlite | think\db\builder\Sqlite |
Sqlsrv | think\db\builder\Sqlsrv |
這些生成類都繼承了核心提供的生成器基類think\db\Builder
,每個生成器類只需要提供差異部分的實現(xiàn)醇滥。
數(shù)據(jù)庫配置
數(shù)據(jù)庫的配置參數(shù)有很大的學(xué)問黎比,也是你掌握數(shù)據(jù)庫操作的基礎(chǔ)超营,主要用于數(shù)據(jù)庫的連接以及查詢的相關(guān)設(shè)置。
數(shù)據(jù)庫的配置參數(shù)用于連接類的架構(gòu)方法阅虫,而由于我們并不直接操作連接類演闭,所以,配置參數(shù)主要通過Db
類傳入并設(shè)置到當(dāng)前的數(shù)據(jù)庫連接類颓帝。
數(shù)據(jù)庫配置分為靜態(tài)配置和動態(tài)配置兩種方式米碰,靜態(tài)配置是指在數(shù)據(jù)庫配置文件中進行配置,動態(tài)配置是指在Db類或者Query
類的connect
方法中傳入動態(tài)的配置參數(shù)购城。
安裝好ThinkPHP5之后见间,默認(rèn)在application
目錄下面會有一個database.php
文件,這就是應(yīng)用的數(shù)據(jù)庫配置文件工猜,如果你的模塊需要單獨的數(shù)據(jù)庫配置文件米诉,那么只需要在模塊目錄下面創(chuàng)建一個database.php
文件即可,并且只需要定義和應(yīng)用數(shù)據(jù)庫配置文件有差異的部分篷帅。
數(shù)據(jù)庫配置文件中配置的是默認(rèn)的數(shù)據(jù)庫連接配置史侣,如果你有多個數(shù)據(jù)庫連接,額外的數(shù)據(jù)庫連接是在應(yīng)用配置文件中完成的(參考后面的動態(tài)數(shù)據(jù)庫連接)魏身。
├─application
│ ├─index
│ │ ├─database.php (模塊)數(shù)據(jù)庫配置文件
│ │ └─ ...
│ ├─database.php (應(yīng)用)數(shù)據(jù)庫配置文件
│ └─ ...
我們下面的數(shù)據(jù)庫配置文件都以應(yīng)用數(shù)據(jù)庫配置文件為例說明惊橱。
默認(rèn)的應(yīng)用數(shù)據(jù)庫配置文件如下:
return [
// 數(shù)據(jù)庫類型
'type' => 'mysql',
// 服務(wù)器地址
'hostname' => '127.0.0.1',
// 數(shù)據(jù)庫名
'database' => '',
// 用戶名
'username' => 'root',
// 密碼
'password' => '',
// 端口
'hostport' => '',
// 連接dsn
'dsn' => '',
// 數(shù)據(jù)庫連接參數(shù)
'params' => [],
// 數(shù)據(jù)庫編碼默認(rèn)采用utf8
'charset' => 'utf8',
// 數(shù)據(jù)庫表前綴
'prefix' => '',
// 數(shù)據(jù)庫調(diào)試模式
'debug' => true,
// 數(shù)據(jù)庫部署方式:0 集中式(單一服務(wù)器),1 分布式(主從服務(wù)器)
'deploy' => 0,
// 數(shù)據(jù)庫讀寫是否分離 主從式有效
'rw_separate' => false,
// 讀寫分離后 主服務(wù)器數(shù)量
'master_num' => 1,
// 指定從服務(wù)器序號
'slave_no' => '',
// 是否嚴(yán)格檢查字段是否存在
'fields_strict' => true,
// 數(shù)據(jù)集返回類型
'resultset_type' => 'array',
// 自動寫入時間戳字段
'auto_timestamp' => false,
// 時間字段取出后的默認(rèn)時間格式
'datetime_format' => 'Y-m-d H:i:s',
// 是否需要進行SQL性能分析
'sql_explain' => false,
// Builder類
'builder' => '',
// Query類
'query' => '\\think\\db\\Query',
];
最關(guān)鍵的參數(shù)就是下面幾個(其它參數(shù)后面會陸續(xù)涉及):
參數(shù)名 | 作用 |
---|---|
type | 數(shù)據(jù)庫類型或者連接類名 |
hostname | 數(shù)據(jù)庫服務(wù)器地址(一般是IP地址,默認(rèn)為127.0.0.1 ) |
username | 數(shù)據(jù)庫用戶名(默認(rèn)為root ) |
password | 數(shù)據(jù)庫用戶密碼(默認(rèn)為空) |
database | 使用的數(shù)據(jù)庫名稱 |
charset | 數(shù)據(jù)庫編碼(默認(rèn)為utf8 ) |
type
參數(shù)嚴(yán)格來說其實配置的是連接類名(而不是數(shù)據(jù)庫類型)箭昵,支持命名空間完整定義税朴,不帶命名空間定義的話,默認(rèn)采用\think\db\connector
作為命名空間(內(nèi)置連接類的命名空間)家制。你完全可以在應(yīng)用中擴展自己的數(shù)據(jù)庫連接類正林,例如配置為:
// 配置數(shù)據(jù)庫類型(連接類)為自定義
'type' => '\app\db\Mysql',
這樣就可以自己替換或者擴展一些額外的數(shù)據(jù)庫操作方法。
自定義連接類的時候颤殴,請注意設(shè)置數(shù)據(jù)庫配置中的
builder
參數(shù)避免找不到對應(yīng)生成器類觅廓。
ThinkPHP5.0
采用PDO來統(tǒng)一操作數(shù)據(jù)庫,而連接類的最關(guān)鍵的作用就是通過配置連接到數(shù)據(jù)庫涵但,PDO的連接方法參數(shù)如下:
PDO::__construct ( 'DSN' ,'用戶名','密碼','連接參數(shù)(數(shù)組)' )
數(shù)據(jù)庫的數(shù)據(jù)源名稱(DSN
)是最關(guān)鍵的一個參數(shù)杈绸,連接類負責(zé)把數(shù)據(jù)庫配置參數(shù)自動轉(zhuǎn)換為一個有效的DSN
數(shù)據(jù)源名稱。如果你有特殊的連接語法需求矮瘟,則可以通過配置數(shù)據(jù)庫配置文件中的dsn
參數(shù)來解決瞳脓,該配置參數(shù)的值會直接用于PDO連接,例如:
// 連接dsn
'dsn' => 'mysql:unix_socket=/tmp/mysql.sock;dbname=demo',
數(shù)據(jù)庫支持?jǐn)嗑€重連機制(默認(rèn)關(guān)閉)澈侠,可以設(shè)置(V5.0.6+
版本僅支持Mysql數(shù)據(jù)庫劫侧,V5.0.9+
版本開始支持內(nèi)置所有數(shù)據(jù)庫):
// 開啟斷線重連
'break_reconnect' => true,
除了DSN
數(shù)據(jù)源名稱,PDO
的連接參數(shù)也可以單獨設(shè)置埋涧,每個連接驅(qū)動都有自己的連接參數(shù)設(shè)置板辽,Mysql
連接器內(nèi)置采用的參數(shù)包括如下:
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES => false,
可以在數(shù)據(jù)庫配置文件中設(shè)置params
參數(shù)奇瘦,會和內(nèi)置的連接參數(shù)合并,例如:
// 數(shù)據(jù)庫連接參數(shù)
'params' => [
// 使用長連接
\PDO::ATTR_PERSISTENT => true,
// 數(shù)據(jù)表字段統(tǒng)一轉(zhuǎn)換為小寫
\PDO::ATTR_CASE => \PDO::CASE_LOWER,
],
常用數(shù)據(jù)庫連接參數(shù)(params)可以參考PHP在線手冊中的以PDO::ATTR_
開頭的常量劲弦。
如何開始查詢
在開始學(xué)習(xí)查詢之前耳标,我們首先在demo
數(shù)據(jù)庫中創(chuàng)建一個data
測試表。
CREATE TABLE IF NOT EXISTS `data`(
`id` int(8) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL COMMENT '名稱',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
然后設(shè)置數(shù)據(jù)庫配置文件內(nèi)容為(如果有密碼請自行修改):
return [
// 數(shù)據(jù)庫類型
'type' => 'mysql',
// 服務(wù)器地址
'hostname' => '127.0.0.1',
// 數(shù)據(jù)庫名
'database' => 'demo',
// 用戶名
'username' => 'root',
// 密碼
'password' => '',
// 開啟數(shù)據(jù)庫調(diào)試
'debug' => true,
];
特別注意我們在配置中開啟了
debug
參數(shù)邑跪,表示開啟數(shù)據(jù)庫的調(diào)試模型次坡,開啟后會記錄數(shù)據(jù)庫的連接信息和SQL日志,數(shù)據(jù)庫的調(diào)試模式和應(yīng)用的調(diào)試模式是兩個不同的概念画畅。
配置完數(shù)據(jù)庫連接信息后砸琅,我們就可以直接使用Db
類進行數(shù)據(jù)庫運行原生SQL操作了,你無需關(guān)心數(shù)據(jù)庫的連接操作轴踱,系統(tǒng)會自動使用數(shù)據(jù)庫配置參數(shù)進行數(shù)據(jù)庫的連接操作症脂。
Db
類的方法都是靜態(tài)調(diào)用(不需要去實例化think\Db
類),Db
類的查詢方法有很多(大部分查詢都是使用的查詢構(gòu)造器)淫僻,本章內(nèi)容暫時只講兩個用于原生查詢的方法诱篷,包括query
(查詢操作)和execute
(寫入操作),更多的查詢方法會在查詢構(gòu)造器章節(jié)作出詳細講解雳灵。
數(shù)據(jù)庫查詢的所有示例都需要寫到一個控制器的方法里面棕所,我們現(xiàn)在假設(shè)你已經(jīng)定義了一個下面的控制器操作方法:
<?php
namespace app\index\controller;
use think\Db;
class Index
{
public function index()
{
// 這里是數(shù)據(jù)庫操作的測試代碼
// ...
return;
}
}
一般來說并不建議在控制器的操作方法中直接操作數(shù)據(jù)庫Db類,但由于我們還沒涉及到模型章節(jié)的內(nèi)容悯辙,因此琳省,目前的寫法僅為了演示數(shù)據(jù)庫的示例代碼。
并且在應(yīng)用配置文件中開啟頁面Trace顯示:
// 應(yīng)用Trace
'app_trace' => true,
開啟頁面Trace的作用是為了方便我們查看當(dāng)前請求的SQL語句信息以及執(zhí)行時間(需要開啟數(shù)據(jù)庫調(diào)試模式后有效)躲撰。
然后在index
操作方法中添加下面測試代碼:
Db::execute('insert into data (id, name) values (1, "hinkphp")');
Db::query('select * from data where id=1');
訪問頁面后荡灾,顯示空白瓤狐,點擊右下角的對數(shù)據(jù)表的CURD操作针贬,除了
select
和存儲過程調(diào)用使用query
方法之外,其它的操作都使用execute
方法茴肥,這里就不再一一演示了坚踩。
就可以打開頁面Trace信息,切換到SQL一欄批幌,可以看到下面的類似信息
第一條表示數(shù)據(jù)庫的連接信息(連接消耗時間以及連接的DSN)础锐,后面的兩條就表示當(dāng)前操作執(zhí)行的SQL語句,由于我們使用的是原生查詢荧缘,所以SQL語句和你的代碼里面的SQL語句是一致的皆警,每條SQL語句最后會顯示該SQL語句的執(zhí)行消耗時間。
細心的朋友會發(fā)現(xiàn)Db
類里面并沒有query
和execute
方法截粗,其實在調(diào)用Db
類的方法(connect
方法除外)之前信姓,都會先調(diào)用connect
方法進行數(shù)據(jù)庫的初始化(前面提過的__callStatic
方法)鸵隧,由于connect
方法會返回一個數(shù)據(jù)庫連接類的對象實例(根據(jù)配置參數(shù)實現(xiàn)了單例),所以Db
類調(diào)用的query
和execute
方法其實就是連接器類(Connection
)的方法意推,這一點必須理解豆瘫,否則你很難理解數(shù)據(jù)庫的查詢操作。
使用參數(shù)綁定
上面的例子是實際開發(fā)中其實并不建議菊值,原則上我們在使用原生查詢的時候最好使用參數(shù)綁定避免SQL注入外驱,例如:
Db::execute('insert into data (id, name) values (?, ?)',[2,'kancloud']);
Db::query('select * from data where id=?',[2]);
頁面Trace信息中會顯示實際運行的SQL語句
也支持命名占位符綁定,例如:
Db::execute('insert into data (id, name) values (:id, :name)',['id'=>3,'name'=>'topthink']);
Db::query('select * from data where id=:id',['id'=>3]);
參數(shù)綁定的變量不需要使用引號
同樣顯示的實際執(zhí)行SQL如下:
我們看到查詢語句中的id的值是字符串的腻窒,由于參數(shù)綁定默認(rèn)都是使用的字符串昵宇,如果需要指定為數(shù)字類型,可以使用下面的方式:
Db::execute('insert into data (id, name) values (:id, :name)',['id'=>[4,\PDO::PARAM_INT],'name'=>'onethink']);
Db::query('select * from data where id=:id',['id'=>[4,\PDO::PARAM_INT]]);
這次查看實際的執(zhí)行SQL會有細微的變化
PDO命名占位綁定不支持一個參數(shù)多處綁定儿子,下面的用法會報錯:
Db::execute('insert into data (name) values (:name),(:name)',['name'=>'thinkphp']);
該錯誤信息表示你的參數(shù)綁定參數(shù)數(shù)量不符瓦哎。
查詢返回值
使用Db
類查詢數(shù)據(jù)庫的話,query
方法的返回值是一個二維數(shù)組的數(shù)據(jù)集柔逼,每個元素就是一條記錄杭煎,例如:
array (size=1)
0 =>
array (size=5)
'id' => int 8
'name' => string 'thinkphp' (length=8)
如果沒有查詢到任何數(shù)據(jù),返回值就是一個空數(shù)組卒落。
相比query
方法羡铲,execute
方法的返回值就比較單純,一般就是返回影響(包括新增和更新)的記錄數(shù)儡毕,如果沒有影響任何記錄也切,則返回值為0
,所以千萬不要用布爾值來判斷execute
是否執(zhí)行成功腰湾,事實上雷恃,在5.0
里面不需要判斷是否成功,因為如果發(fā)生錯誤一定會拋出異常费坊。
動態(tài)連接數(shù)據(jù)庫
當(dāng)你需要使用多個數(shù)據(jù)庫連接的時候倒槐,就需要使用connect
方法動態(tài)切換到另外一個數(shù)據(jù)庫連接,假設(shè)存在另外一個數(shù)據(jù)庫test
附井,并且復(fù)制data
過去更名為test
讨越,然后測試下面的示例:
Db::query('select * from data where id = 2');
Db::connect([
// 數(shù)據(jù)庫類型
'type' => 'mysql',
// 服務(wù)器地址
'hostname' => '127.0.0.1',
// 數(shù)據(jù)庫名
'database' => 'test',
// 用戶名
'username' => 'root',
// 密碼
'password' => '',
// 開啟調(diào)試模式
'debug' => true,
])->query('select * from test where id = 1');
Db::query('select * from data where id = 3');
頁面Trace的顯示信息可以看出來使用了兩次數(shù)據(jù)庫連接和執(zhí)行了三次查詢,并且數(shù)據(jù)庫連接切換并沒有影響默認(rèn)的查詢(第三個查詢還是使用的默認(rèn)數(shù)據(jù)庫配置連接永毅,test
數(shù)據(jù)庫中并不存在data
表把跨,如果連接的還是第二個數(shù)據(jù)庫連接的話肯定會報錯)。
有時候沼死,我們只需要設(shè)置一些基本的數(shù)據(jù)庫配置參數(shù)着逐,可以簡化成一個字符串格式定義(該格式為ThinkPHP使用規(guī)范,而不是PDO連接規(guī)范,不要和DSN
混淆起來):
Db::connect('mysql://root@127.0.0.1/demo#utf8')
->query('select * from data where id = 1');
字符串格式的連接信息規(guī)范格式如下:
數(shù)據(jù)庫類型://用戶名[:用戶密碼]@數(shù)據(jù)庫服務(wù)器地址[:端口]/數(shù)據(jù)庫名[?參數(shù)1=值&參數(shù)2=值]#數(shù)據(jù)庫編碼
Db
類的connect
方法會返回一個數(shù)據(jù)庫連接對象實例耸别,相同的連接參數(shù)返回的是同一個對象實例健芭,除非你強制重新實例化,例如:
Db::connect([
// 數(shù)據(jù)庫類型
'type' => 'mysql',
// 服務(wù)器地址
'hostname' => '127.0.0.1',
// 數(shù)據(jù)庫名
'database' => 'demo',
// 用戶名
'username' => 'root',
// 密碼
'password' => '',
],true)->query('select * from data where id = 1');
這樣秀姐,每次調(diào)用都會重新實例化數(shù)據(jù)庫的連接類吟榴。
為了便于統(tǒng)一管理,你可以把數(shù)據(jù)庫配置參數(shù)納入配置文件囊扳,例如在應(yīng)用配置文件中添加:
'db_config' => [
// 數(shù)據(jù)庫類型
'type' => 'mysql',
// 服務(wù)器地址
'hostname' => '127.0.0.1',
// 數(shù)據(jù)庫名
'database' => 'demo',
// 用戶名
'username' => 'root',
// 密碼
'password' => '',
],
或者使用字符串方式定義
'db_config' => 'mysql://root@127.0.0.1/demo',
上面的
db_config
配置參數(shù)不是在數(shù)據(jù)庫配置文件中定義吩翻,而是在應(yīng)用配置文件或者模塊配置文件中定義。
然后锥咸,使用下面的方式來動態(tài)連接獲取切換連接
Db::connect('db_config')
->query('select * from data where id=:id', ['id'=>3]);
當(dāng)connect
方法傳入的連接參數(shù)是字符串并且不包含/
等特殊符號的話狭瞎,表示使用的是預(yù)定義數(shù)據(jù)庫配置參數(shù)。
分布式支持
數(shù)據(jù)訪問層支持分布式數(shù)據(jù)庫搏予,包括讀寫分離熊锭,要啟用分布式數(shù)據(jù)庫,需要開啟數(shù)據(jù)庫配置文件中的deploy
參數(shù):
return [
// 啟用分布式數(shù)據(jù)庫
'deploy' => 1,
// 數(shù)據(jù)庫類型
'type' => 'mysql',
// 服務(wù)器地址
'hostname' => '192.168.1.1,192.168.1.2',
// 數(shù)據(jù)庫名
'database' => 'demo',
// 數(shù)據(jù)庫用戶名
'username' => 'root',
// 數(shù)據(jù)庫密碼
'password' => '',
// 數(shù)據(jù)庫連接端口
'hostport' => '',
];
啟用分布式數(shù)據(jù)庫后雪侥,hostname
參數(shù)是關(guān)鍵碗殷,hostname
的個數(shù)決定了分布式數(shù)據(jù)庫的數(shù)量,默認(rèn)情況下第一個地址就是主服務(wù)器速缨。
主從服務(wù)器支持設(shè)置不同的連接參數(shù)锌妻,包括:
連接參數(shù) |
---|
username |
password |
hostport |
database |
dsn |
charset |
如果主從服務(wù)器的上述參數(shù)一致的話,只需要設(shè)置一個旬牲,對于不同的參數(shù)仿粹,可以分別設(shè)置,例如:
return [
// 啟用分布式數(shù)據(jù)庫
'deploy' => 1,
// 數(shù)據(jù)庫類型
'type' => 'mysql',
// 服務(wù)器地址
'hostname' => '192.168.1.1,192.168.1.2,192.168.1.3',
// 數(shù)據(jù)庫名
'database' => 'demo',
// 數(shù)據(jù)庫用戶名
'username' => 'root,slave,slave',
// 數(shù)據(jù)庫密碼
'password' => '123456',
// 數(shù)據(jù)庫連接端口
'hostport' => '',
// 數(shù)據(jù)庫字符集
'charset' => 'utf8',
];
記住原茅,要么相同吭历,要么每個都要設(shè)置。
還可以設(shè)置分布式數(shù)據(jù)庫的讀寫是否分離擂橘,默認(rèn)的情況下讀寫不分離晌区,也就是每臺服務(wù)器都可以進行讀寫操作,對于主從式數(shù)據(jù)庫而言通贞,需要設(shè)置讀寫分離朗若,通過下面的設(shè)置就可以:
'rw_separate' => true,
在讀寫分離的情況下,默認(rèn)第一個數(shù)據(jù)庫配置是主服務(wù)器的配置信息滑频,負責(zé)寫入數(shù)據(jù)捡偏,如果設(shè)置了master_num
參數(shù),則可以支持多個主服務(wù)器寫入(每次隨機連接其中一個主服務(wù)器)峡迷。其它的地址都是從數(shù)據(jù)庫,負責(zé)讀取數(shù)據(jù),數(shù)量不限制绘搞。每次連接從服務(wù)器并且進行讀取操作的時候彤避,系統(tǒng)會隨機進行在從服務(wù)器中選擇。同一個數(shù)據(jù)庫連接的每次請求只會連接一次主服務(wù)器和從服務(wù)器夯辖,如果某次請求的從服務(wù)器連接不上琉预,會自動切換到主服務(wù)器進行查詢操作。
如果不希望隨機讀取蒿褂,或者某種情況下其它從服務(wù)器暫時不可用圆米,還可以設(shè)置slave_no
指定固定服務(wù)器進行讀操作,slave_no
指定的序號表示hostname
中數(shù)據(jù)庫地址的序號啄栓,從0
開始娄帖。
調(diào)用查詢類或者模型的CURD
操作的話,系統(tǒng)會自動判斷當(dāng)前執(zhí)行的方法是讀操作還是寫操作并自動連接主從服務(wù)器昙楚,如果你用的是原生SQL近速,那么需要注意系統(tǒng)的默認(rèn)規(guī)則: 寫操作必須用數(shù)據(jù)庫的execute
方法,讀操作必須用數(shù)據(jù)庫的query
方法堪旧,否則會發(fā)生主從讀寫錯亂的情況削葱。
發(fā)生下列情況的話,會自動連接主服務(wù)器:
- 使用了數(shù)據(jù)庫的寫操作方法(
execute
/insert
/update
/delete
以及衍生方法)淳梦; - 如果調(diào)用了數(shù)據(jù)庫事務(wù)方法的話析砸,會自動連接主服務(wù)器;
- 從服務(wù)器連接失敗爆袍,會自動連接主服務(wù)器干厚;
- 調(diào)用了查詢構(gòu)造器的
lock
方法; - 調(diào)用了查詢構(gòu)造器的
master
方法
主從數(shù)據(jù)庫的數(shù)據(jù)同步工作不在框架實現(xiàn)螃宙,需要數(shù)據(jù)庫考慮自身的同步或者復(fù)制機制蛮瞄。如果在大數(shù)據(jù)量或者特殊的情況下寫入數(shù)據(jù)后可能會存在同步延遲的情況,可以調(diào)用
master()
方法進行主庫查詢操作谆扎。
在實際生產(chǎn)環(huán)境中挂捅,很多云主機的數(shù)據(jù)庫分布式實現(xiàn)機制和本地開發(fā)會有所區(qū)別,但通常會采下面用兩種方式:
- 第一種:提供了寫IP和讀IP(一般是虛擬IP)堂湖,進行數(shù)據(jù)庫的讀寫分離操作闲先;
- 第二種:始終保持同一個IP連接數(shù)據(jù)庫,內(nèi)部會進行讀寫分離IP調(diào)度(阿里云就是采用該方式)无蜂。
存儲過程調(diào)用
數(shù)據(jù)訪問層支持存儲過程調(diào)用伺糠,調(diào)用數(shù)據(jù)庫存儲過程使用下面的方法:
$resultSet = Db::query('call procedure_name');
foreach ($resultSet as $result) {
}
存儲過程返回的是一個數(shù)據(jù)集,如果你的存儲過程不需要返回任何的數(shù)據(jù)斥季,那么也可以使用execute
方法:
Db::execute('call procedure_name');
存儲過程可以支持輸入和輸出參數(shù)训桶,以及進行參數(shù)綁定操作累驮。
$resultSet = Db::query('call procedure_name(:in_param1,:in_param2,:out_param)', [
'in_param1' => $param1,
'in_param2' => [$param2, PDO::PARAM_INT],
'out_param' => [$outParam, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 4000],
]);
輸出參數(shù)的綁定必須額外使用PDO::PARAM_INPUT_OUTPUT
,并且可以和輸入?yún)?shù)公用一個參數(shù)舵揭。
無論存儲過程內(nèi)部做了什么操作谤专,每次存儲過程調(diào)用僅僅被當(dāng)成一次查詢。
數(shù)據(jù)庫事務(wù)
5.0對數(shù)據(jù)庫事務(wù)的封裝更為完善午绳,事務(wù)的支持由連接器類來完成置侍,但查詢器類中也對事務(wù)進行了封裝調(diào)用,不過我們?nèi)匀恢恍枰ㄟ^Db類便可完成事務(wù)操作拦焚。
使用事務(wù)處理的話蜡坊,需要數(shù)據(jù)庫引擎支持事務(wù)處理。比如
MySQL
的MyISAM
類型不支持事務(wù)處理赎败,需要使用InnoDB
引擎秕衙。
最簡單的方法是使用transaction
方法操作數(shù)據(jù)庫事務(wù),會自動控制事務(wù)處理螟够,當(dāng)發(fā)生任何異常會自動回滾灾梦,例如:
Db::transaction(function () {
Db::table('user')->find(1);
Db::table('user')->where('id', 1)->save(['name' => 'thinkphp']);
Db::table('user')->delete(1);
});
也可以手動控制事務(wù),例如:
// 啟動事務(wù)
Db::startTrans();
try{
Db::table('user')->find(1);
Db::table('user')->where('id',1)->save(['name'=>'thinkphp']);
Db::table('user')->delete(1);
// 提交事務(wù)
Db::commit();
} catch (\Exception $e) {
// 回滾事務(wù)
Db::rollback();
}
在事務(wù)操作的時候妓笙,確保你的數(shù)據(jù)庫連接是同一個若河,否則事務(wù)會失效,
V5.0.9
版本之前的db
助手函數(shù)都是默認(rèn)重新鏈接數(shù)據(jù)庫的寞宫,請不要在事務(wù)中使用萧福。
總結(jié)
通過本章的學(xué)習(xí),你應(yīng)該了解了5.0的數(shù)據(jù)庫架構(gòu)設(shè)計和數(shù)據(jù)庫抽象訪問層的組成辈赋,以及如何配置數(shù)據(jù)庫信息和使用基礎(chǔ)的原生查詢鲫忍,掌握了用Db
類的connect
方法切換不同的數(shù)據(jù)庫連接,基本了解了存儲過程及事務(wù)的用法钥屈。后面一章悟民,我們會先來了解下數(shù)據(jù)庫的創(chuàng)建和數(shù)據(jù)遷移,之后就會進入真正的數(shù)據(jù)庫查詢的學(xué)習(xí)了篷就。