亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

寫一個“特殊”的查詢構造器 - (二、第一條語句)

dadong / 1653人閱讀

摘要:注在常駐內存單例模式下,這種多次用一個類進行查詢的情形很常見。斷線重連對于典型環(huán)境而言,一次的查詢已經隨著的請求而結束,的垃圾回收功能會回收一次請求周期內的數據。但在常駐內存的環(huán)境下,尤其是單例模式下,數據庫驅動類可能一直在內存中不被銷毀。

構造、執(zhí)行第一條語句

上一篇完成了代碼結構的搭建和 PDO 的基礎封裝,這一篇我們來講如何構造一個最基本的 SQL 語句,并執(zhí)行得到結果。

query sql 構造目標: SELECT * FROM test_table;

查詢構造器執(zhí)行語法構造目標: $drivers->table("test_table")->select("*")->get();

測試用的數據表請大家自己建立,這里就不多帶帶演示了。

我們回顧下 PDO 執(zhí)行這個 query 語句的基本用法:

1、PDO::query() 方法獲取結果集:

$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() 方法提供了防注入、參數綁定的機制,可以指定結果集的返回格式,更加靈活易于封裝,我們選這種。

query sql 字符串構造:

要構造 query sql 語句,那么不妨先觀察一下它的基本構造:

SELECT、 要查找的字段(列)、 FROM、 要查找的表、 關聯子句、 條件子句、 分組子句、 排序子句、 LIMIT 子句。

除了 SELECT 和 FROM 是固定不變,我們只需構造好查詢字段、表名和一系列子句的字符串,然后按照 query sql 的語法拼接在一起即可。

在基類 PDODriver.php 中添加屬性作為構造字符串:

protected $_table = "";        // table 名
protected $_prepare_sql = "";  // prepare 方法執(zhí)行的 sql 語句
protected $_cols_str = " * ";  // 需要查詢的字段,默認為 * (全部)
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 子句
基礎方法的創(chuàng)建

有了基本的構造字符串屬性,可以開始構造一條 sql 了。

添加 _buildQuery() 方法,用來構造 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() 方法,用來設置表名:

public function table($table)
{
    $this->_table = $table;

    return $this; // 為了鏈式調用,返回當前實例
}

添加 select() 方法,這里使用可變參數靈活處理傳入:

public function select()
{
    // 獲取傳入方法的所有參數
    $cols = func_get_args();

    if( ! func_num_args() || in_array("*", $cols)) {
        // 如果沒有傳入參數,默認查詢全部字段
        $this->_cols_str = " * ";
    } else {

        $this->_cols_str = ""; // 清除默認的 * 值
        // 構造 "field1, filed2 ..." 字符串
        foreach ($cols as $col) {
            $this->_cols_str .= " ".$col.",";
        }
        $this->_cols_str = rtrim($this->_cols_str, ",");
    }

    return $this;
}
構造、執(zhí)行

sql 字符串構造完畢,接下來就需要一個執(zhí)行 sql 并取得結果的方法來收尾。

添加 get() 方法:

public function get()
{
    try {
        $this->_buildQuery();   // 構建 sql
        // prepare 預處理
        $pdoSt = $this->_pdo->prepare($this->_prepare_sql);
        // 執(zhí)行
        $pdoSt->execute();
    } catch (PDOException $e) {
        throw $e;
    }

    return $pdoSt->fetchAll(PDO::FETCH_ASSOC); // 獲取一個以鍵值數組形式的結果集
}
測試

修改 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 屬性默認為 " * ",所以在查詢全部字段時省略 select() 方法的調用也是可以的。

之后為了節(jié)省篇幅,一些通用的方法只使用 Mysql 驅動類作為測試對象,PostgreSql 和 Sqlite 請讀者自己進行測試,之后不會再多帶帶說明。

優(yōu)化 1、解耦

get 方法中的 prepare、execute 過程是通用的 (查詢、插入、刪除、更新等操作),我們可以將這部分代碼提出來,在其它執(zhí)行 sql 取結果的方法中復用。

基類中新建 _execute() 方法:

protected function _execute()
{
    try {
        $this->_pdoSt = $this->_pdo->prepare($this->_prepare_sql);
        $this->_pdoSt->execute();

    } catch (PDOException $e) {
        throw $e;
    }
}

由于將邏輯分離到另一個方法中,get() 方法獲取不到 PDOStatement 實例,因此將 PDOStatement 實例保存到基類的屬性中:

protected $_pdoSt = NULL;

修改后的 get() 方法:

public function get()
{
    $this->_buildQuery();
    $this->_execute();

    return $this->_pdoSt->fetchAll(PDO::FETCH_ASSOC);
}
2、參數重置

使用查詢構造器一次查詢后,各個構造字符串的內容已經被修改,為了不影響下一次查詢,需要將這些構造字符串恢復到初始狀態(tài)。

注:在常駐內存單例模式下,這種多次用一個類進行查詢的情形很常見。

添加 _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 后將各構造字符串恢復初始狀態(tài),保證下一次查詢的正確性
    } catch (PDOException $e) {
        throw $e;
    }

}
row() 方法

上述的 get() 方法是直接取得整個結果集。而有一些業(yè)務邏輯希望只取一行結果,那么就需要一個 row() 方法來實現這個需求了。

row() 方法并不難,只需把 get() 方法中的 PDOStatement::fetchAll() 方法改為 PDOStatement::fetch() 方法即可:

public function row()
{
    $this->_buildQuery();
    $this->_execute();

    return $this->_pdoSt->fetch(PDO::FETCH_ASSOC);
}

這里就不多說了,大家可以自己測試一下結果。

斷線重連

對于典型 web 環(huán)境而言,一次 sql 的查詢已經隨著 HTTP 的請求而結束,PHP 的垃圾回收功能會回收一次請求周期內的數據。而一次 HTTP 請求的時間也相對較短,基本不用考慮數據庫斷線的問題。

但在常駐內存的環(huán)境下,尤其是單例模式下,數據庫驅動類可能一直在內存中不被銷毀。如果很長時間內沒有對數據庫進行訪問的話,由數據庫驅動類建立的數據庫連接會被數據庫作為空閑連接切斷 (具體時間由數據庫設置決定),此時如果依舊使用舊的連接對象,會出現持續(xù)報錯的問題。也就是說,我們要對數據庫斷線的情況進行處理,在檢測到斷線的同時新建一個連接代替舊的連接繼續(xù)使用?!?】

在 PDO 中,數據庫斷線后繼續(xù)訪問會相應的拋出一個 PDOException 異常 (也可以是一個錯誤,由 PDO 的錯誤處理設置決定)。

當數據庫出現錯誤時,PDOException 實例的 errorInfo 屬性中保存了錯誤的詳細信息數組,第一個元素返回 SQLSTATE error code,第二個元素是具體驅動錯誤碼,第三個元素是具體的錯誤信息。參見 PDO::errorInfo

Mysql 斷線相關的錯誤碼有兩個:

2006 CR_SERVER_GONE_ERROR

2013 CR_SERVER_LOST

PostgreSql 斷線相關的錯誤碼有一個:

當具體驅動錯誤碼為 7 時 PostgreSql 斷線 (此驅動錯誤碼根據 PDOException 實測得出,暫時未找到相關文檔)

Sqlite 基于內存和文件,不存在斷線一說,不做考慮。

這里我們使用 PDO 的具體驅動錯誤碼作為判斷斷線的依據。

基類添加 _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 拋出異常,判斷是否是數據庫斷線引起
        if($this->_isTimeout($e)) { 
            // 斷線異常,清除舊的數據庫連接,重新連接
            $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 的原生接口也支持斷線重連:

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;
        }
    }
}

如何模擬斷線?

在內存常駐模式中 (如 workerman 的 server 監(jiān)聽環(huán)境下):

訪問數據庫

重啟服務器的數據庫軟件

再次訪問數據庫,看看是否能正常獲取數據。

Just do it

參考

【1】Workerman mysql

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://www.ezyhdfw.cn/yun/30829.html

相關文章

  • 一個特殊查詢造器 - (前言)

    摘要:而在項目開發(fā)中,我們想要的是一個更好用的可維護的工具,此時,對代碼的封裝模塊化就顯得尤為重要,于是出現了兩種方案查詢構造器,對象關系映射。典型環(huán)境下按照一般的查詢構造器處理就行。 文章目錄 寫一個特殊的查詢構造器 - (前言) 寫一個特殊的查詢構造器 - (一、程序結構,基礎封裝) 寫一個特殊的查詢構造器 - (二、第一條語句) 寫一個特殊的查詢構造器 - (三、條件查詢) 寫一個特殊...

    GitChat 評論0 收藏0
  • 一個特殊查詢造器 - (四、條件查詢:復雜條件)

    摘要:復雜的條件在的條件查詢中,不只有這些基本的子句,還有等復雜一些的子句。這篇我們就來講一下查詢構造器如何構造這些復雜的查詢語句。 復雜的條件 在 SQL 的條件查詢中,不只有 where、or where 這些基本的子句,還有 where in、where exists、where between 等復雜一些的子句。而且即使是 where 這種基礎的子句,也有多個條件的多種邏輯組合。這篇...

    baoxl 評論0 收藏0
  • 一個特殊查詢造器 - (七、DML 語句、事務)

    摘要:同樣的,添加屬性,修改函數構造語句的方法基類中添加方法檢測有沒有設置子句構建語句參數綁定返回影響的行數更新數據示例相比,語句更為簡單,只需子句即可。 查詢語句 (DQL) 的構造功能開發(fā)完畢,我們再給查詢構造器增加一些對 DML (Data Manipulation Language) 語句的支持,如簡單的 insert、update、delete 操作。 insert 我們先回顧下 ...

    lookSomeone 評論0 收藏0

發(fā)表評論

0條評論

dadong

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<