使用事务

在上一节中,我们重申了确保更新或删除查询包含理想的匹配记录集是多么重要。尽管这一点始终适用,但有一种方法可以还原刚才所做的更改,那就是使用事务。

事务是一种状态,在这种状态下,MySQL 会跟踪你对数据所做的所有更改,以便在需要时恢复所有更改。你需要明确地启动一个事务,在关闭与服务器的连接之前,你需要提交你的更改。这意味着,在你告诉它这样做之前,MySQL 不会真正执行这些更改。如果在事务过程中想恢复更改,应回滚而不是提交。

PDO 有三个函数允许你这样做:

  • beginTransaction(开始交易):这将启动事务。

  • commit:将提交更改。请记住,如果您不提交,PHP 脚本结束或您明确关闭连接,MySQL 将拒绝您在此事务中做出的所有更改。

  • rollBack:回滚此事务中的所有更改。

在应用程序中使用事务的一种可能情况是,您需要执行多个查询,而且所有查询都必须成功,否则就不能执行整组查询。在数据库中添加销售时就是这种情况。请记住,我们的销售额存储在两个表中:一个是销售额本身,另一个是与该销售额相关的图书列表。在添加新的销售时,需要确保所有图书都已添加到该数据库中;否则,销售将被破坏。您需要做的是执行所有查询,检查它们的返回值。如果其中任何一个返回值为 false,则整个销售都应回滚。

让我们在 init.php 文件中创建一个 addSale 函数来模拟这种行为。内容如下

function addSale(int $userId, array $bookIds): void {
    $db = new PDO(
        'mysql:host=127.0.0.1;dbname=bookstore',
        'root',
        ''
    );

    $db->beginTransaction();
    try {
        $query = 'INSERT INTO sale (customer_id, date) '. 'VALUES(:id, NOW())';
        $statement = $db->prepare($query);
        if (!$statement->execute(['id' => $userId])) {
            throw new Exception($statement->errorInfo()[2]);
        }
        $saleId = $db->lastInsertId();

        $query = 'INSERT INTO sale_book (book_id, sale_id) ' . 'VALUES(:book, :sale)';
        $statement = $db->prepare($query);
        $statement->bindValue('sale', $saleId);
        foreach ($bookIds as $bookId) {
            $statement->bindValue('book', $bookId);
            if (!$statement->execute()) {
                throw new Exception($statement->errorInfo()[2]);
            }
        }

        $db->commit();
    } catch (Exception $e) {
        $db->rollBack();
        throw $e;
    }
}

这个函数相当复杂。它的参数包括客户 ID 和书籍列表,因为我们假定销售日期是当前日期。我们要做的第一件事就是连接数据库,实例化 PDO 类。随后,我们将开始事务处理,该事务处理仅在本函数执行期间持续进行。开始事务后,我们将打开一个 try…​catch 块,该块将包含函数的其余代码。原因是如果我们抛出异常,catch 块将捕获异常,回滚事务并传播异常。try 代码块中的代码首先添加销售,然后遍历图书列表,并将它们插入数据库。在任何时候,我们都会检查执行函数的响应,如果响应为假,我们就会抛出异常,并告知错误信息。

让我们尝试使用这个函数。编写以下代码,尝试为三本书添加销售;但其中一本不存在,即 ID 为 200 的那本:

try {
    addSale(1, [1, 2, 200]);
} catch (Exception $e) {
    echo 'Error adding sale: ' . $e->getMessage();
}

这段代码将回显错误信息,抱怨不存在书籍。如果在 MySQL 中检查,销售表中将没有记录,因为在抛出异常时函数回滚了。

最后,让我们试试下面的代码。这段代码将添加三本有效的书籍,这样查询就总是成功的,而且 try 代码块可以一直运行到最后,我们将在最后提交更改:

try {
    addSale(1, [1, 2, 3]);
} catch (Exception $e) {
    echo 'Error adding sale: ' . $e->getMessage();
}

测试一下,你会发现浏览器上没有打印任何信息。然后,进入数据库,确保有一个新的销售行,并且有三本书与之相连。

总结

在本章中,我们了解了数据库的重要性以及如何在网络应用程序中使用数据库:从使用 PDO 设置连接、按需创建和获取数据,到构建更复杂的查询以满足我们的需求。有了这些,我们的应用程序现在看起来比完全静态的时候更有用了。

在下一章中,我们将了解如何通过模型视图控制器(MVC)应用网络应用程序最重要的设计模式。用这种方式组织应用程序时,你将获得代码清晰的感觉。