相關學習推薦:mysql教學
關於預處理語句我們在上篇教學中已經簡單介紹過,我們可以將其與視圖模板類比,所謂預處理語句就是預先定義的SQL 語句模板,其中的具體參數值透過佔位符取代:
INSERT INTO REGISTRY (name, value) VALUES (?, ?) INSERT INTO REGISTRY (name, value) VALUES (:name, :value)
然後在後續真正要執行SQL 語句之前,再透過特定API 方法將特定參數值與對應佔位符進行綁定與對應。就好比定義的視圖模板也是將變數透過特定佔位符替代,然後真正渲染時將變數值傳遞進來填充和渲染一樣。
為什麼要費這番周折呢?直接用前面示範的 query
方法進行增刪改查操作它不香嗎?呃,那我們接下來來說說預處理語句的好處,或者說為什麼要使用預處理語句進行資料庫交互,好處有二:
綜上,從效能和安全性角度考慮,建議使用預處理語句處理資料庫的增刪改查操作。
接下來,我們基於PDO 提供的預處理語句API 實作MySQL 資料庫的增刪改查操作,我們將透過物件導向的方式來實作:
<?php class Post { public $id; public $title; public $content; public $created_at; /** * @var PDO */ protected $pdo; public function __construct(PDO $pdo = null) { if ($pdo != null) { $this->pdo = $pdo; } } public function insert($title, $content) { $sql = 'INSERT INTO `post` (title, content, created_at) VALUES (:title, :content, :created_at)'; try { // 准备预处理语句 $stmt = $this->pdo->prepare($sql); // 获取当前时间对应的格式化字符串:2020-05-28 13:00:00 $datetime = date('Y-m-d H:i:s', time()); // 绑定参数值 $stmt->bindParam(':title', $title, PDO::PARAM_STR); $stmt->bindParam(':content', $content, PDO::PARAM_STR); $stmt->bindParam(':created_at', $datetime, PDO::PARAM_STR); // 执行语句 $stmt->execute(); return $this->pdo->lastInsertId(); // 返回插入记录对应ID } catch (PDOException $e) { printf("数据库插入失败: %s\n", $e->getMessage()); } } public function select($id) { $sql = 'SELECT * FROM `post` WHERE id = ?'; try { // 准备预处理语句 $stmt = $this->pdo->prepare($sql); // 绑定参数值 $stmt->bindValue(1, $id, PDO::PARAM_INT); // 执行语句 $stmt->execute(); return $stmt->fetchObject(self::class); // 以对象方式返回结果集 } catch (PDOException $e) { printf("数据库查询失败: %s\n", $e->getMessage()); } } public function selectAll() { $sql = 'SELECT * FROM `post` ORDER BY id DESC'; try { // 准备预处理语句 $stmt = $this->pdo->prepare($sql); // 执行语句 $stmt->execute(); return $stmt->fetchAll(); // 返回所有结果集 } catch (PDOException $e) { printf("数据库查询失败: %s\n", $e->getMessage()); } } public function update($id) { $sql = 'UPDATE `post` SET created_at = :created_at WHERE id = :id'; try { // 准备预处理语句 $stmt = $this->pdo->prepare($sql); $datetime = date('Y-m-d H:i:s', time()); // 绑定参数值 $stmt->bindParam(':created_at', $datetime, PDO::PARAM_STR); $stmt->bindValue(':id', $id, PDO::PARAM_INT); // 执行语句 $stmt->execute(); return $stmt->rowCount(); } catch (PDOException $e) { printf("数据库更新失败: %s\n", $e->getMessage()); } } public function delete($id) { $sql = 'DELETE FROM `post` WHERE id = ?'; try { // 准备预处理语句 $stmt = $this->pdo->prepare($sql); // 绑定参数值 $stmt->bindValue(1, $id, PDO::PARAM_INT); // 执行语句 $stmt->execute(); return $stmt->rowCount(); } catch (PDOException $e) { printf("数据库删除失败: %s\n", $e->getMessage()); } } }
我們建立了一個Post
類,然後在建構函式中初始化$pdo
實例(從外部傳入),然後將基於預處理語句實現的增刪改查操作分解到對應的類別方法中。整體邏輯非常簡單,以insert
為例,首先透過PDO 物件的prepare 方法傳入SQL 範本建構預處理語句,該方法傳回PDOStatement 對象,接下來,就是呼叫該物件的bindParam 方法綁定具體參數值,此方法的第一個參數是佔位符,第二個參數是參數值,第三個參數是值類型(對應的常數可以在PDO 預定義常數中查詢),綁定好參數後,就可以呼叫PDOStatement 物件的execute 方法執行預處理語句了。
對於插入操作,可以透過 PDO 物件上的 lastInsertId 方法傳回插入記錄的主鍵 ID,對於更新和刪除方法,可以透過 PDOStatement 物件上的 rowCount 方法傳回受影響行數表示是否操作成功。對於查詢操作,可以透過 PDOStatement 物件的 fetch 方法傳回單一記錄,也可以透過 fetchObject 方法傳回對應到指定類別後的物件實例(也是單一記錄),對於多個結果,可以透過 fetchAll 方法傳回。
要注意的是,在宣告預處理語句的時候,可以透過?
佔位符,也可以透過:name
這種可讀性更好的佔位符,然後在綁定參數時,既可以透過bindValue 也可以透過bindParam 方法,兩者傳遞參數一樣,只是對於?
佔位符,需要透過數值序號建立與SQL 模板的映射(從1 開始)。
結合程式碼和PHP 官方文件理解上面的程式碼並不困難,接下來,我們來寫測試程式碼:
// 初始化 PDO 连接实例 $dsn = 'mysql:host=127.0.0.1;port=3306;dbname=test;charset=utf8mb4'; $user = 'root'; $pass = 'root'; try { $pdo = new PDO($dsn, $user, $pass); } catch (PDOException $e) { printf("数据库连接失败: %s\n", $e->getMessage()); } // 测试代码 $post = new Post($pdo); // insert $title = '这是一篇测试文章'; $content = '测试内容: 今天天气不错'; $id = $post->insert($title, $content); echo '文章插入成功: ' . $id . '<br>'; // select $item = $post->select($id); echo '<pre class="brush:php;toolbar:false">'; print_r($item); // update $affected = $post->update($id); echo '受影响的行数: ' . $affected . '<br>'; // delete $affected = $post->delete($id); echo '受影响的行数: ' . $affected . '<br>'; // selectAll $items = $post->selectAll(); print_r($items);
初始化一個PDO 物件實例傳入Post
建構函數,然後依序呼叫Post 物件的增刪改查方法。在瀏覽器中訪問,列印結果如下:
我們可以看到fetchAll
方法預設傳回的結果集數組中既包含索引映射,又包含欄位名稱映射,這可以透過設定取得模式來解決,例如要傳回Post
物件數組,可以這麼做:
return $stmt->fetchAll(PDO::FETCH_CLASS, self::class);
這樣,傳回的結果就是這樣的了:
更多模式設置,請參考官方文件中fetchAll 方法的介紹和範例。
最後,我們再來看看如何透過PDO 擴展實現資料庫事務的提交和回滾,我們已經知道,對於單一SQL 語句而言,事務提交和回滾是自動完成的,對於SQL 語句序列(多條SQL 語句),則需要明確開啟事務和提交事務,PDO 物件也為此提供了對應的API 方法。非常簡單,例如我們在 Post
類別中新增一個批次插入方法 batchInsert
方法:
public function batchInsert(array $items) { $sql = 'INSERT INTO `post` (title, content, created_at) VALUES (:title, :content, :created_at)'; try { // 开启事务 $this->pdo->beginTransaction(); // 准备预处理语句 $stmt = $this->pdo->prepare($sql); foreach ($items as $item) { // 绑定参数值 $datetime = date('Y-m-d H:i:s', time()); $stmt->bindParam(':title', $item->title, PDO::PARAM_STR); $stmt->bindParam(':content', $item->content, PDO::PARAM_STR); $stmt->bindParam(':created_at', $datetime, PDO::PARAM_STR); // 执行语句 $stmt->execute(); } $this->pdo->commit(); // 提交事务 return $stmt->rowCount(); // 返回受影响的行数 } catch (PDOException $e) { $this->pdo->rollBack(); // 回滚事务 printf("数据库批量插入失败: %s\n", $e->getMessage()); } }
我们只需要在执行 SQL 序列之前调用 PDO 对象的 beginTransaction 方法开启事务,然后在所有 SQL 语句执行完成后调用 commit 方法提交事务,如果 SQL 执行过程中出错,则在异常处理代码中通过 PDO 对象的 rollBack 方法回滚事务。
为上述方法编写测试代码:
$post = new Post($pdo); $items = [ [ 'title' => '这是一篇测试文章111', 'content' => '测试内容' ], [ 'title' => '这是一篇测试文章222', 'content' => '测试内容' ], [ 'title' => '这是一篇测试文章333', 'content' => '测试内容' ], ]; $post->batchInsert($items); $items = $post->selectAll(); print_r($items);
执行这段代码,打印结果中包含新插入的文章数据,则表明事务提交成功:
关于通过 PDO 扩展与 MySQL 数据库交互,我们就简单介绍到这里,更多细节可以阅读官方文档,相信通过这几个课程的学习,你已经对 MySQL 数据库的基本使用以及如何在 PHP 中连接数据库并进行增删改查有了初步的认知,从下篇教程开始,我们将结合具体实战项目来开发一个现代的 PHP 项目,将之前的学习到的知识点应用到实战中,并且引入一些现代的 PHP 理念对项目进行管理。
想了解更多相关文章,敬请关注php mysql栏目!
以上是透過PDO擴充與MySQL資料庫互動 實現增刪改查實作與資料庫事務的詳細內容。更多資訊請關注PHP中文網其他相關文章!