摘要:注在常駐內(nèi)存單例模式下,這種多次用一個(gè)類進(jìn)行查詢的情形很常見(jiàn)。斷線重連對(duì)于典型環(huán)境而言,一次的查詢已經(jīng)隨著的請(qǐng)求而結(jié)束,的垃圾回收功能會(huì)回收一次請(qǐng)求周期內(nèi)的數(shù)據(jù)。但在常駐內(nèi)存的環(huán)境下,尤其是單例模式下,數(shù)據(jù)庫(kù)驅(qū)動(dòng)類可能一直在內(nèi)存中不被銷毀。
構(gòu)造、執(zhí)行第一條語(yǔ)句
上一篇完成了代碼結(jié)構(gòu)的搭建和 PDO 的基礎(chǔ)封裝,這一篇我們來(lái)講如何構(gòu)造一個(gè)最基本的 SQL 語(yǔ)句,并執(zhí)行得到結(jié)果。
query sql 構(gòu)造目標(biāo): SELECT * FROM test_table;
查詢構(gòu)造器執(zhí)行語(yǔ)法構(gòu)造目標(biāo): $drivers->table("test_table")->select("*")->get();
測(cè)試用的數(shù)據(jù)表請(qǐng)大家自己建立,這里就不多帶帶演示了。
我們回顧下 PDO 執(zhí)行這個(gè) query 語(yǔ)句的基本用法:
1、PDO::query() 方法獲取結(jié)果集:
$pdo->query("SELECT * FROM test_table;");
2、PDO::prepare()、PDOStatement::execute() 方法:
$pdoSt = $pdo->prepare("SELECT * FROM test_table;"); $pdoSt->execute(); $pdoSt->fetchAll(PDO::FETCH_ASSOC);
PDO::prepare() 方法提供了防注入、參數(shù)綁定的機(jī)制,可以指定結(jié)果集的返回格式,更加靈活易于封裝,我們選這種。
query sql 字符串構(gòu)造:要構(gòu)造 query sql 語(yǔ)句,那么不妨先觀察一下它的基本構(gòu)造:
SELECT、 要查找的字段(列)、 FROM、 要查找的表、 關(guān)聯(lián)子句、 條件子句、 分組子句、 排序子句、 LIMIT 子句。
除了 SELECT 和 FROM 是固定不變,我們只需構(gòu)造好查詢字段、表名和一系列子句的字符串,然后按照 query sql 的語(yǔ)法拼接在一起即可。
在基類 PDODriver.php 中添加屬性作為構(gòu)造字符串:
protected $_table = ""; // table 名 protected $_prepare_sql = ""; // prepare 方法執(zhí)行的 sql 語(yǔ)句 protected $_cols_str = " * "; // 需要查詢的字段,默認(rèn)為 * (全部) protected $_where_str = ""; // where 子句 protected $_orderby_str = ""; // order by 子句 protected $_groupby_str = ""; // group by 子句 protected $_having_str = ""; // having 子句 (配合 group by 使用) protected $_join_str = ""; // join 子句 protected $_limit_str = ""; // limit 子句基礎(chǔ)方法的創(chuàng)建
有了基本的構(gòu)造字符串屬性,可以開(kāi)始構(gòu)造一條 sql 了。
添加 _buildQuery() 方法,用來(lái)構(gòu)造 sql 字符串:
protected function _buildQuery() { $this->_prepare_sql = "SELECT ".$this->_cols_str." FROM ".$this->_table. $this->_join_str. $this->_where_str. $this->_groupby_str.$this->_having_str. $this->_orderby_str. $this->_limit_str; }
添加 table() 方法,用來(lái)設(shè)置表名:
public function table($table) { $this->_table = $table; return $this; // 為了鏈?zhǔn)秸{(diào)用,返回當(dāng)前實(shí)例 }
添加 select() 方法,這里使用可變參數(shù)靈活處理傳入:
public function select() { // 獲取傳入方法的所有參數(shù) $cols = func_get_args(); if( ! func_num_args() || in_array("*", $cols)) { // 如果沒(méi)有傳入?yún)?shù),默認(rèn)查詢?nèi)孔侄? $this->_cols_str = " * "; } else { $this->_cols_str = ""; // 清除默認(rèn)的 * 值 // 構(gòu)造 "field1, filed2 ..." 字符串 foreach ($cols as $col) { $this->_cols_str .= " ".$col.","; } $this->_cols_str = rtrim($this->_cols_str, ","); } return $this; }構(gòu)造、執(zhí)行
sql 字符串構(gòu)造完畢,接下來(lái)就需要一個(gè)執(zhí)行 sql 并取得結(jié)果的方法來(lái)收尾。
添加 get() 方法:
public function get() { try { $this->_buildQuery(); // 構(gòu)建 sql // prepare 預(yù)處理 $pdoSt = $this->_pdo->prepare($this->_prepare_sql); // 執(zhí)行 $pdoSt->execute(); } catch (PDOException $e) { throw $e; } return $pdoSt->fetchAll(PDO::FETCH_ASSOC); // 獲取一個(gè)以鍵值數(shù)組形式的結(jié)果集 }測(cè)試
修改 test/test.php:
require_once dirname(dirname(__FILE__)) . "/vendor/autoload.php"; use DriversMysql; $config = [ "host" => "localhost", "port" => "3306", "user" => "username", "password" => "password", "dbname" => "database", "charset" => "utf8", "timezone" => "+8:00", "collection" => "utf8_general_ci", "strict" => false, ]; $driver = new Mysql($config); // 執(zhí)行 SELECT * FROM test_table; 的查詢 $results = $driver->table("test_table")->select("*")->get(); var_dump($results);
注:上述代碼中由于 _cols_str 屬性默認(rèn)為 " * ",所以在查詢?nèi)孔侄螘r(shí)省略 select() 方法的調(diào)用也是可以的。
之后為了節(jié)省篇幅,一些通用的方法只使用 Mysql 驅(qū)動(dòng)類作為測(cè)試對(duì)象,PostgreSql 和 Sqlite 請(qǐng)讀者自己進(jìn)行測(cè)試,之后不會(huì)再多帶帶說(shuō)明。
優(yōu)化 1、解耦get 方法中的 prepare、execute 過(guò)程是通用的 (查詢、插入、刪除、更新等操作),我們可以將這部分代碼提出來(lái),在其它執(zhí)行 sql 取結(jié)果的方法中復(fù)用。
基類中新建 _execute() 方法:
protected function _execute() { try { $this->_pdoSt = $this->_pdo->prepare($this->_prepare_sql); $this->_pdoSt->execute(); } catch (PDOException $e) { throw $e; } }
由于將邏輯分離到另一個(gè)方法中,get() 方法獲取不到 PDOStatement 實(shí)例,因此將 PDOStatement 實(shí)例保存到基類的屬性中:
protected $_pdoSt = NULL;
修改后的 get() 方法:
public function get() { $this->_buildQuery(); $this->_execute(); return $this->_pdoSt->fetchAll(PDO::FETCH_ASSOC); }2、參數(shù)重置
使用查詢構(gòu)造器一次查詢后,各個(gè)構(gòu)造字符串的內(nèi)容已經(jīng)被修改,為了不影響下一次查詢,需要將這些構(gòu)造字符串恢復(fù)到初始狀態(tài)。
注:在常駐內(nèi)存單例模式下,這種多次用一個(gè)類進(jìn)行查詢的情形很常見(jiàn)。
添加 _reset() 方法:
protected function _reset() { $this->_table = ""; $this->_prepare_sql = ""; $this->_cols_str = " * "; $this->_where_str = ""; $this->_orderby_str = ""; $this->_groupby_str = ""; $this->_having_str = ""; $this->_join_str = ""; $this->_limit_str = ""; $this->_bind_params = []; }
修改 _execute() 方法:
protected function _execute() { try { $this->_pdoSt = $this->_pdo->prepare($this->_prepare_sql); $this->_pdoSt->execute(); $this->_reset(); // 每次執(zhí)行 sql 后將各構(gòu)造字符串恢復(fù)初始狀態(tài),保證下一次查詢的正確性 } catch (PDOException $e) { throw $e; } }row() 方法
上述的 get() 方法是直接取得整個(gè)結(jié)果集。而有一些業(yè)務(wù)邏輯希望只取一行結(jié)果,那么就需要一個(gè) row() 方法來(lái)實(shí)現(xiàn)這個(gè)需求了。
row() 方法并不難,只需把 get() 方法中的 PDOStatement::fetchAll() 方法改為 PDOStatement::fetch() 方法即可:
public function row() { $this->_buildQuery(); $this->_execute(); return $this->_pdoSt->fetch(PDO::FETCH_ASSOC); }
這里就不多說(shuō)了,大家可以自己測(cè)試一下結(jié)果。
斷線重連對(duì)于典型 web 環(huán)境而言,一次 sql 的查詢已經(jīng)隨著 HTTP 的請(qǐng)求而結(jié)束,PHP 的垃圾回收功能會(huì)回收一次請(qǐng)求周期內(nèi)的數(shù)據(jù)。而一次 HTTP 請(qǐng)求的時(shí)間也相對(duì)較短,基本不用考慮數(shù)據(jù)庫(kù)斷線的問(wèn)題。
但在常駐內(nèi)存的環(huán)境下,尤其是單例模式下,數(shù)據(jù)庫(kù)驅(qū)動(dòng)類可能一直在內(nèi)存中不被銷毀。如果很長(zhǎng)時(shí)間內(nèi)沒(méi)有對(duì)數(shù)據(jù)庫(kù)進(jìn)行訪問(wèn)的話,由數(shù)據(jù)庫(kù)驅(qū)動(dòng)類建立的數(shù)據(jù)庫(kù)連接會(huì)被數(shù)據(jù)庫(kù)作為空閑連接切斷 (具體時(shí)間由數(shù)據(jù)庫(kù)設(shè)置決定),此時(shí)如果依舊使用舊的連接對(duì)象,會(huì)出現(xiàn)持續(xù)報(bào)錯(cuò)的問(wèn)題。也就是說(shuō),我們要對(duì)數(shù)據(jù)庫(kù)斷線的情況進(jìn)行處理,在檢測(cè)到斷線的同時(shí)新建一個(gè)連接代替舊的連接繼續(xù)使用。【1】
在 PDO 中,數(shù)據(jù)庫(kù)斷線后繼續(xù)訪問(wèn)會(huì)相應(yīng)的拋出一個(gè) PDOException 異常 (也可以是一個(gè)錯(cuò)誤,由 PDO 的錯(cuò)誤處理設(shè)置決定)。
當(dāng)數(shù)據(jù)庫(kù)出現(xiàn)錯(cuò)誤時(shí),PDOException 實(shí)例的 errorInfo 屬性中保存了錯(cuò)誤的詳細(xì)信息數(shù)組,第一個(gè)元素返回 SQLSTATE error code,第二個(gè)元素是具體驅(qū)動(dòng)錯(cuò)誤碼,第三個(gè)元素是具體的錯(cuò)誤信息。參見(jiàn) PDO::errorInfo
Mysql 斷線相關(guān)的錯(cuò)誤碼有兩個(gè):
2006 CR_SERVER_GONE_ERROR
2013 CR_SERVER_LOST
PostgreSql 斷線相關(guān)的錯(cuò)誤碼有一個(gè):
當(dāng)具體驅(qū)動(dòng)錯(cuò)誤碼為 7 時(shí) PostgreSql 斷線 (此驅(qū)動(dòng)錯(cuò)誤碼根據(jù) PDOException 實(shí)測(cè)得出,暫時(shí)未找到相關(guān)文檔)
Sqlite 基于內(nèi)存和文件,不存在斷線一說(shuō),不做考慮。
這里我們使用 PDO 的具體驅(qū)動(dòng)錯(cuò)誤碼作為判斷斷線的依據(jù)。
基類添加 _isTimeout() 方法:
protected function _isTimeout(PDOException $e) { // 異常信息滿足斷線條件,則返回 true return ( $e->errorInfo[1] == 2006 || // MySQL server has gone away (CR_SERVER_GONE_ERROR) $e->errorInfo[1] == 2013 || // Lost connection to MySQL server during query (CR_SERVER_LOST) $e->errorInfo[1] == 7 // no connection to the server (for postgresql) ); }
修改 _execute() 方法,添加斷線重連功能:
protected function _execute() { try { $this->_pdoSt = $this->_pdo->prepare($this->_prepare_sql); $this->_pdoSt->execute(); $this->_reset(); } catch (PDOException $e) { // PDO 拋出異常,判斷是否是數(shù)據(jù)庫(kù)斷線引起 if($this->_isTimeout($e)) { // 斷線異常,清除舊的數(shù)據(jù)庫(kù)連接,重新連接 $this->_closeConnection(); $this->_connect(); // 重試異常前的操作 try { $this->_pdoSt = $this->_pdo->prepare($this->_prepare_sql); $this->_pdoSt->execute(); $this->_reset(); } catch (PDOException $e) { // 還是失敗、向外拋出異常 throw $e; } } else { // 非斷線引起的異常,向外拋出,交給外部邏輯處理 throw $e; } } }
順便把之前暴露的 PDO 的原生接口也支持?jǐn)嗑€重連:
public function query($sql) { try { return $this->_pdo->query($sql); } catch (PDOException $e) { // when time out, reconnect if($this->_isTimeout($e)) { $this->_closeConnection(); $this->_connect(); try { return $this->_pdo->query($sql); } catch (PDOException $e) { throw $e; } } else { throw $e; } } } public function exec($sql) { try { return $this->_pdo->exec($sql); } catch (PDOException $e) { // when time out, reconnect if($this->_isTimeout($e)) { $this->_closeConnection(); $this->_connect(); try { return $this->_pdo->exec($sql); } catch (PDOException $e) { throw $e; } } else { throw $e; } } } public function prepare($sql, array $driver_options = []) { try { return $this->_pdo->prepare($sql, $driver_options); } catch (PDOException $e) { // when time out, reconnect if($this->_isTimeout($e)) { $this->_closeConnection(); $this->_connect(); try { return $this->_pdo->prepare($sql, $driver_options); } catch (PDOException $e) { throw $e; } } else { throw $e; } } }
如何模擬斷線?
在內(nèi)存常駐模式中 (如 workerman 的 server 監(jiān)聽(tīng)環(huán)境下):
訪問(wèn)數(shù)據(jù)庫(kù)
重啟服務(wù)器的數(shù)據(jù)庫(kù)軟件
再次訪問(wèn)數(shù)據(jù)庫(kù),看看是否能正常獲取數(shù)據(jù)。
Just do it
參考【1】Workerman mysql
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/30829.html
摘要:而在項(xiàng)目開(kāi)發(fā)中,我們想要的是一個(gè)更好用的可維護(hù)的工具,此時(shí),對(duì)代碼的封裝模塊化就顯得尤為重要,于是出現(xiàn)了兩種方案查詢構(gòu)造器,對(duì)象關(guān)系映射。典型環(huán)境下按照一般的查詢構(gòu)造器處理就行。 文章目錄 寫(xiě)一個(gè)特殊的查詢構(gòu)造器 - (前言) 寫(xiě)一個(gè)特殊的查詢構(gòu)造器 - (一、程序結(jié)構(gòu),基礎(chǔ)封裝) 寫(xiě)一個(gè)特殊的查詢構(gòu)造器 - (二、第一條語(yǔ)句) 寫(xiě)一個(gè)特殊的查詢構(gòu)造器 - (三、條件查詢) 寫(xiě)一個(gè)特殊...
摘要:復(fù)雜的條件在的條件查詢中,不只有這些基本的子句,還有等復(fù)雜一些的子句。這篇我們就來(lái)講一下查詢構(gòu)造器如何構(gòu)造這些復(fù)雜的查詢語(yǔ)句。 復(fù)雜的條件 在 SQL 的條件查詢中,不只有 where、or where 這些基本的子句,還有 where in、where exists、where between 等復(fù)雜一些的子句。而且即使是 where 這種基礎(chǔ)的子句,也有多個(gè)條件的多種邏輯組合。這篇...
摘要:同樣的,添加屬性,修改函數(shù)構(gòu)造語(yǔ)句的方法基類中添加方法檢測(cè)有沒(méi)有設(shè)置子句構(gòu)建語(yǔ)句參數(shù)綁定返回影響的行數(shù)更新數(shù)據(jù)示例相比,語(yǔ)句更為簡(jiǎn)單,只需子句即可。 查詢語(yǔ)句 (DQL) 的構(gòu)造功能開(kāi)發(fā)完畢,我們?cè)俳o查詢構(gòu)造器增加一些對(duì) DML (Data Manipulation Language) 語(yǔ)句的支持,如簡(jiǎn)單的 insert、update、delete 操作。 insert 我們先回顧下 ...
閱讀 1662·2021-09-26 09:55
閱讀 5278·2021-09-22 15:40
閱讀 2022·2019-08-30 15:53
閱讀 1505·2019-08-30 11:15
閱讀 1723·2019-08-29 15:41
閱讀 1878·2019-08-28 18:13
閱讀 3154·2019-08-26 12:00
閱讀 1678·2019-08-26 10:30