通过PDO扩展与MySQL数据库交互 实现增删改查实现和数据库事务
相關(guān)學(xué)習(xí)推薦:mysql教程
通過預(yù)處理語句進(jìn)行增刪改查為什么使用預(yù)處理語句
關(guān)于預(yù)處理語句我們在上篇教程中已經(jīng)簡單介紹過,我們可以將其與視圖模板類比,所謂預(yù)處理語句就是預(yù)定義的 SQL 語句模板,其中的具體參數(shù)值通過占位符替代:
INSERT INTO REGISTRY (name, value) VALUES (?, ?) INSERT INTO REGISTRY (name, value) VALUES (:name, :value)
然后在后續(xù)真正要執(zhí)行 SQL 語句之前,再通過特定 API 方法將具體參數(shù)值與對應(yīng)占位符進(jìn)行綁定和映射。就好比定義的視圖模板也是將變量通過特定占位符替代,然后真正渲染時將變量值傳遞進(jìn)來填充和渲染一樣。
為什么要費這番周折呢?直接用前面演示的 query 方法進(jìn)行增刪改查操作它不香嗎?呃,那我們接下來來說說預(yù)處理語句的好處,或者說為什么要使用預(yù)處理語句進(jìn)行數(shù)據(jù)庫交互,好處有二:
首先,使用預(yù)處理語句提前定義的 SQL 模板只會解析一次,但可以通過傳遞不同的參數(shù)值執(zhí)行多次,從而避免模板相同的 SQL 語句重復(fù)分析、編譯和優(yōu)化,提高數(shù)據(jù)庫操作執(zhí)行速度;其次,后期傳遞給預(yù)處理語句的參數(shù)值會被底層驅(qū)動進(jìn)行處理,從而有效避免 SQL 注入攻擊。
綜上,從性能和安全角度考慮,推薦使用預(yù)處理語句處理數(shù)據(jù)庫的增刪改查操作。
增刪改查示例代碼
接下來,我們基于 PDO 提供的預(yù)處理語句 API 實現(xiàn) MySQL 數(shù)據(jù)庫的增刪改查操作,我們將通過面向?qū)ο蟮姆绞絹韺崿F(xiàn):
<?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 {
// 準(zhǔn)備預(yù)處理語句
$stmt = $this->pdo->prepare($sql);
// 獲取當(dāng)前時間對應(yīng)的格式化字符串:2020-05-28 13:00:00
$datetime = date('Y-m-d H:i:s', time());
// 綁定參數(shù)值
$stmt->bindParam(':title', $title, PDO::PARAM_STR);
$stmt->bindParam(':content', $content, PDO::PARAM_STR);
$stmt->bindParam(':created_at', $datetime, PDO::PARAM_STR);
// 執(zhí)行語句
$stmt->execute();
return $this->pdo->lastInsertId(); // 返回插入記錄對應(yīng)ID
} catch (PDOException $e) {
printf("數(shù)據(jù)庫插入失敗: %s\\n", $e->getMessage());
}
}
public function select($id)
{
$sql = 'SELECT * FROM `post` WHERE id = ?';
try {
// 準(zhǔn)備預(yù)處理語句
$stmt = $this->pdo->prepare($sql);
// 綁定參數(shù)值
$stmt->bindValue(1, $id, PDO::PARAM_INT);
// 執(zhí)行語句
$stmt->execute();
return $stmt->fetchObject(self::class); // 以對象方式返回結(jié)果集
} catch (PDOException $e) {
printf("數(shù)據(jù)庫查詢失敗: %s\\n", $e->getMessage());
}
}
public function selectAll()
{
$sql = 'SELECT * FROM `post` ORDER BY id DESC';
try {
// 準(zhǔn)備預(yù)處理語句
$stmt = $this->pdo->prepare($sql);
// 執(zhí)行語句
$stmt->execute();
return $stmt->fetchAll(); // 返回所有結(jié)果集
} catch (PDOException $e) {
printf("數(shù)據(jù)庫查詢失敗: %s\\n", $e->getMessage());
}
}
public function update($id)
{
$sql = 'UPDATE `post` SET created_at = :created_at WHERE id = :id';
try {
// 準(zhǔn)備預(yù)處理語句
$stmt = $this->pdo->prepare($sql);
$datetime = date('Y-m-d H:i:s', time());
// 綁定參數(shù)值
$stmt->bindParam(':created_at', $datetime, PDO::PARAM_STR);
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
// 執(zhí)行語句
$stmt->execute();
return $stmt->rowCount();
} catch (PDOException $e) {
printf("數(shù)據(jù)庫更新失敗: %s\\n", $e->getMessage());
}
}
public function delete($id)
{
$sql = 'DELETE FROM `post` WHERE id = ?';
try {
// 準(zhǔn)備預(yù)處理語句
$stmt = $this->pdo->prepare($sql);
// 綁定參數(shù)值
$stmt->bindValue(1, $id, PDO::PARAM_INT);
// 執(zhí)行語句
$stmt->execute();
return $stmt->rowCount();
} catch (PDOException $e) {
printf("數(shù)據(jù)庫刪除失敗: %s\\n", $e->getMessage());
}
}
}
我們構(gòu)建了一個 Post 類,然后在構(gòu)造函數(shù)中初始化 $pdo 實例(從外部傳入),然后將基于預(yù)處理語句實現(xiàn)的增刪改查操作分解到對應(yīng)的類方法中。整體邏輯非常簡單,以 insert 為例,首先通過 PDO 對象的 prepare 方法傳入 SQL 模板構(gòu)建預(yù)處理語句,該方法返回 PDOStatement 對象,接下來,就是調(diào)用該對像的 bindParam 方法綁定具體參數(shù)值,該方法的第一個參數(shù)是占位符,第二個參數(shù)是參數(shù)值,第三個參數(shù)是值類型(對應(yīng)的常量可以在 PDO 預(yù)定義常量中查詢),綁定好參數(shù)后,就可以調(diào)用 PDOStatement 對象的 execute 方法執(zhí)行預(yù)處理語句了。
對于插入操作,可以通過 PDO 對象上的 lastInsertId 方法返回插入記錄的主鍵 ID,對于更新和刪除方法,可以通過 PDOStatement 對象上的 rowCount 方法返回受影響行數(shù)表示是否操作成功。對于查詢操作,可以通過 PDOStatement 對象的 fetch 方法返回單條記錄,也可以通過 fetchObject 方法返回映射到指定類后的對象實例(也是單條記錄),對于多個結(jié)果,可以通過 fetchAll 方法返回。
需要注意的是,在聲明預(yù)處理語句的時候,可以通過 ? 占位符,也可以通過 :name 這種可讀性更好的占位符,然后在綁定參數(shù)時,既可以通過 bindValue 也可以通過 bindParam 方法,兩者傳遞參數(shù)一樣,只是對于 ? 占位符,需要通過數(shù)值序號建立與 SQL 模板的映射(從 1 開始)。
結(jié)合代碼和 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("數(shù)據(jù)庫連接失敗: %s\\n", $e->getMessage());
}
// 測試代碼
$post = new Post($pdo);
// insert
$title = '這是一篇測試文章';
$content = '測試內(nèi)容: 今天天氣不錯';
$id = $post->insert($title, $content);
echo '文章插入成功: ' . $id . '<br>';
// select
$item = $post->select($id);
echo '<pre>';
print_r($item);
// update
$affected = $post->update($id);
echo '受影響的行數(shù): ' . $affected . '<br>';
// delete
$affected = $post->delete($id);
echo '受影響的行數(shù): ' . $affected . '<br>';
// selectAll
$items = $post->selectAll();
print_r($items);
初始化一個 PDO 對象實例傳入 Post 構(gòu)造函數(shù),然后依次調(diào)用 Post 對象的增刪改查方法。在瀏覽器中訪問,打印結(jié)果如下:
我們可以看到 fetchAll 方法默認(rèn)返回的結(jié)果集數(shù)組中既包含索引映射,又包含字段名映射,這可以通過設(shè)置獲取模式來解決,比如要返回 Post 對象數(shù)組,可以這么做:
return $stmt->fetchAll(PDO::FETCH_CLASS, self::class);
這樣,返回的結(jié)果就是這樣的了:
更多模式設(shè)置,請參考官方文檔中 fetchAll 方法的介紹和示例。
數(shù)據(jù)庫事務(wù)
最后,我們再來看看如何通過 PDO 擴展實現(xiàn)數(shù)據(jù)庫事務(wù)的提交和回滾,我們已經(jīng)知道,對于單條 SQL 語句而言,事務(wù)提交和回滾是自動完成的,對于 SQL 語句序列(多條 SQL 語句),則需要顯式開啟事務(wù)和提交事務(wù),PDO 對象也為此提供了對應(yīng)的 API 方法。非常簡單,比如我們在 Post 類中新增一個批量插入方法 batchInsert 方法:
public function batchInsert(array $items)
{
$sql = 'INSERT INTO `post` (title, content, created_at) VALUES (:title, :content, :created_at)';
try {
// 開啟事務(wù)
$this->pdo->beginTransaction();
// 準(zhǔn)備預(yù)處理語句
$stmt = $this->pdo->prepare($sql);
foreach ($items as $item) {
// 綁定參數(shù)值
$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);
// 執(zhí)行語句
$stmt->execute();
}
$this->pdo->commit(); // 提交事務(wù)
return $stmt->rowCount(); // 返回受影響的行數(shù)
} catch (PDOException $e) {
$this->pdo->rollBack(); // 回滾事務(wù)
printf("數(shù)據(jù)庫批量插入失敗: %s\\n", $e->getMessage());
}
}
我們只需要在執(zhí)行 SQL 序列之前調(diào)用 PDO 對象的 beginTransaction 方法開啟事務(wù),然后在所有 SQL 語句執(zhí)行完成后調(diào)用 commit 方法提交事務(wù),如果 SQL 執(zhí)行過程中出錯,則在異常處理代碼中通過 PDO 對象的 rollBack 方法回滾事務(wù)。
為上述方法編寫測試代碼:
$post = new Post($pdo);
$items = [
[
'title' => '這是一篇測試文章111',
'content' => '測試內(nèi)容'
],
[
'title' => '這是一篇測試文章222',
'content' => '測試內(nèi)容'
],
[
'title' => '這是一篇測試文章333',
'content' => '測試內(nèi)容'
],
];
$post->batchInsert($items);
$items = $post->selectAll();
print_r($items);
執(zhí)行這段代碼,打印結(jié)果中包含新插入的文章數(shù)據(jù),則表明事務(wù)提交成功:
小結(jié)
關(guān)于通過 PDO 擴展與 MySQL 數(shù)據(jù)庫交互,我們就簡單介紹到這里,更多細(xì)節(jié)可以閱讀官方文檔,相信通過這幾個課程的學(xué)習(xí),你已經(jīng)對 MySQL 數(shù)據(jù)庫的基本使用以及如何在 PHP 中連接數(shù)據(jù)庫并進(jìn)行增刪改查有了初步的認(rèn)知,從下篇教程開始,我們將結(jié)合具體實戰(zhàn)項目來開發(fā)一個現(xiàn)代的 PHP 項目,將之前的學(xué)習(xí)到的知識點應(yīng)用到實戰(zhàn)中,并且引入一些現(xiàn)代的 PHP 理念對項目進(jìn)行管理。
想了解更多相關(guān)文章,敬請關(guān)注php mysql欄目!
總結(jié)
以上是生活随笔為你收集整理的通过PDO扩展与MySQL数据库交互 实现增删改查实现和数据库事务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 得流量者得天下吗
- 下一篇: 深访5位百万粉丝创作者,我们总结出城市爆