Rumah  >  Artikel  >  pangkalan data  >  通过PDO扩展与MySQL数据库交互 实现增删改查实现和数据库事务

通过PDO扩展与MySQL数据库交互 实现增删改查实现和数据库事务

coldplay.xixi
coldplay.xixike hadapan
2020-09-09 16:29:093162semak imbas

通过PDO扩展与MySQL数据库交互 实现增删改查实现和数据库事务

相关学习推荐:mysql教程

通过预处理语句进行增删改查

为什么使用预处理语句

关于预处理语句我们在上篇教程中已经简单介绍过,我们可以将其与视图模板类比,所谓预处理语句就是预定义的 SQL 语句模板,其中的具体参数值通过占位符替代:

INSERT INTO REGISTRY (name, value) VALUES (?, ?)
INSERT INTO REGISTRY (name, value) VALUES (:name, :value)

然后在后续真正要执行 SQL 语句之前,再通过特定 API 方法将具体参数值与对应占位符进行绑定和映射。就好比定义的视图模板也是将变量通过特定占位符替代,然后真正渲染时将变量值传递进来填充和渲染一样。

为什么要费这番周折呢?直接用前面演示的 query 方法进行增删改查操作它不香吗?呃,那我们接下来来说说预处理语句的好处,或者说为什么要使用预处理语句进行数据库交互,好处有二:

  • 首先,使用预处理语句提前定义的 SQL 模板只会解析一次,但可以通过传递不同的参数值执行多次,从而避免模板相同的 SQL 语句重复分析、编译和优化,提高数据库操作执行速度;
  • 其次,后期传递给预处理语句的参数值会被底层驱动进行处理,从而有效避免 SQL 注入攻击。

综上,从性能和安全角度考虑,推荐使用预处理语句处理数据库的增删改查操作。

增删改查示例代码

接下来,我们基于 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 对象的增删改查方法。在浏览器中访问,打印结果如下:

-w727

我们可以看到 fetchAll 方法默认返回的结果集数组中既包含索引映射,又包含字段名映射,这可以通过设置获取模式来解决,比如要返回 Post 对象数组,可以这么做:

return $stmt->fetchAll(PDO::FETCH_CLASS, self::class);

这样,返回的结果就是这样的了:

-w505

更多模式设置,请参考官方文档中 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);

执行这段代码,打印结果中包含新插入的文章数据,则表明事务提交成功:

-w846

小结

关于通过 PDO 扩展与 MySQL 数据库交互,我们就简单介绍到这里,更多细节可以阅读官方文档,相信通过这几个课程的学习,你已经对 MySQL 数据库的基本使用以及如何在 PHP 中连接数据库并进行增删改查有了初步的认知,从下篇教程开始,我们将结合具体实战项目来开发一个现代的 PHP 项目,将之前的学习到的知识点应用到实战中,并且引入一些现代的 PHP 理念对项目进行管理。

想了解更多相关文章,敬请关注php mysql栏目!

Atas ialah kandungan terperinci 通过PDO扩展与MySQL数据库交互 实现增删改查实现和数据库事务. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:xueyuanjun.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam
Artikel sebelumnya:mysql如何更改连接端口Artikel seterusnya:mysql的lib文件在哪