【问题标题】:How to make database transaction in PHP OOP如何在 PHP OOP 中进行数据库事务
【发布时间】:2013-12-01 13:26:58
【问题描述】:

在我过时的程序代码(我现在想将其转换为 OOP)中,我有如下简单的数据库事务代码:

mysql_query("BEGIN");
mysql_query("INSERT INTO customers SET cid=$cid,cname='$cname'");
mysql_query("INSERT INTO departments SET did=$did,dname='$dname'");
mysql_query("COMMIT");

如果我构建 OOP 类 CustomerDepartment 来映射 customersdepartments 数据库表,我可以插入表记录如下:

$customer=new Customer();
$customer->setId($cid);
$customer->setName($cname);
$customer->save();

$department=new Department();
$department->setId($did);
$department->setName($dname);
$department->save();

My Customer 和 Department 类在内部使用其他 DB 类来查询数据库。

但是如何使 $customer.save() 和 $department.save() 成为数据库事务的一部分?

我是否应该有一个外部类开始/结束事务,其中实例化了 Customer 和 Department 类,或者事务应该以某种方式在 Customer 中开始(如 Customer.startTransaction())并在 Department 中结束(如 Department.endTransaction())?或者……

【问题讨论】:

  • mysql_* 函数不再维护,不应在任何新代码库中使用。它正在逐步淘汰,以支持更新的 API。相反,您应该将prepared statementsPDOMySQLi 一起使用。
  • 另一方面,你不应该在同一个类中拥有域逻辑和持久性逻辑。尝试实现data mapper 模式(不,它不是 ORM 的名称)。至于您需要在单个事务中存储多个条目的情况,此时您应该开始使用unit of work 来管理您的映射器。
  • 感谢 tereško,我当然会使用 mysqli,但这里的主要问题是如何在一个类上启动数据库事务并将其提交到另一个类!?
  • 您是否真正研究过工作单元是什么以及做什么?在 PoEAA 中有整整一章。
  • 在 php 中的方法是用 -> 操作符调用的,而不是点 (.)

标签: php mysql oop transactions


【解决方案1】:

附加对象是要走的路。像这样的:

$customer=new Customer();
$customer->setId($cid);
$customer->setName($cname);

$department=new Department();
$department->setId($did);
$department->setName($dname);

$transaction = new Transaction();
$transaction->add($customer);
$transaction->add($department);
$transaction->commit();

您可以看到$customer$department 上不再调用save() 方法。 $transaction 对象负责处理。

实现可以这么简单:

class Transaction
{
    private $stack;

    public function __construct()
    {
        $this->stack = array();
    }

    public function add($entity)
    {
        $this->stack[] = $entity;
    }

    public function commit()
    {
        mysql_query("BEGIN");
        foreach ($this->stack as $entity) {
            $entity->save();
        }
        mysql_query("COMMIT");
    }
}

【讨论】:

  • 但您确实应该考虑切换到 PDO 或 MySQLi,因为它们都明确支持事务
【解决方案2】:

如何使 $customer.save() 和 $department.save() 成为数据库事务的一部分?

除了开始交易,你不需要做任何事情。

在大多数 DBMS 接口中,事务对于数据库连接是“全局的”。如果您启动一个事务,那么所有后续工作都会在该事务的范围内自动完成。如果您提交,则您已提交自上次事务开始以来的所有更改。如果您回滚,则放弃自上次 BEGIN 以来的所有更改(也可以选择回滚到上次的 transaction savepoint)。

我只使用了一个数据库 API,它允许每个数据库连接同时激活多个独立事务(即 InterBase / Firebird)。但这并不常见,以至于标准数据库接口(如 ODBC、JDBC、PDO、Perl DBI)只是假设每个数据库连接只能获得一个活动事务,并且所有更改都发生在一个活动事务的范围内。

我是否应该有一个外部类开始/结束事务,其中实例化了 Customer 和 Department 类,或者事务应该以某种方式在 Customer 中开始(如 Customer.startTransaction())并在 Department 中结束(如 Department.endTransaction())?或者……

您应该启动一个事务,然后调用诸如 Customer 和 Department 之类的域模型类,然后在调用代码中提交或回滚事务。

这样做的原因是领域模型方法可以调用其他领域模型方法。你永远不知道这些调用有多深,因此领域模型很难知道何时提交或回滚。

关于这样做的一些陷阱,请参阅How do detect that transaction has already been started?

但他们不必知道这一点。客户和部门应该只做他们的工作,根据需要插入、删除和更新。完成后,调用代码决定是要提交还是回滚整个工作集。

在典型的 PHP 应用程序中,一个事务通常与一个 PHP 请求的工作量相同。在一个给定的 PHP 请求期间执行多个事务是可能的,但并不常见,而且一个事务不可能跨越多个 PHP 请求。

所以简单的答案是你的 PHP 脚本应该在脚本开头附近开始一个事务,在调用任何域模型类之前,然后在脚本末尾提交或回滚,或者一旦域模型类完成它们工作。

【讨论】:

    【解决方案3】:

    您正在迁移到 OOP,这很好,但很快您就会发现自己正在迁移到具有良好区分的数据访问层的架构,包括一种更复杂的将数据与控制分离的方法。现在,我猜你正在使用某种Data access object,这是一个很好的第一种方法模式,但你肯定可以走得更远。这里的一些答案已经将您引向了那个方向。您应该将您的对象视为您的架构的基础,并使用一些辅助对象来查询数据库。相反,您应该考虑一个功能齐全的层,所有必需的通用类负责与数据库的通信,您将在所有项目中使用它,然后拥有业务级对象,如客户或部门,比对数据库实现尽可能少的了解。

    为此,您肯定会有一个处理事务的外部类,但可能还有其他处理安全性的方法,其他用于构建查询的方法,提供独特的 api,无论是数据库引擎,甚至还有一个读取对象的类为了将它们放入数据库中,因此对象本身甚至不知道它应该以数据库结尾。

    实现这一点将是一项艰巨而漫长的工作,但在那之后,您可以拥有一个自定义且可广泛重用的层,这将使您的项目更具可扩展性、更稳定和更值得信赖。那会很棒,你会学到很多东西,之后你会做得很好。您将拥有某种 DBAL 或 ORM。 但这也不是最好的解决方案,因为有些人已经做了很多年了,而且很难实现已经拥有的东西。

    因此,对于任何中等规模的项目,我建议您尽可能认真地对待数据库抽象,以及任何恰好易于使用的开源 ORM,最终您将节省时间并获得系统好多了。

    例如,学说有一种非常好的处理事务和并发的方法,有两种方式:隐式,自动处理正常操作,或隐式,当您需要自己接管和控制事务分界时。 check it out here。此外,还有一些其他复杂的可能性,例如transaction nesting, and others

    最著名和最可靠的ORM是

    我主要使用教义,因为它有一个模块可以与我喜欢的 Zend Framework 2 集成,但 propel 有一些我非常喜欢的方面。

    可能您必须重构某些东西,而此时您不想这样做,但我可以根据我的经验说,这是您甚至不想考虑的事情之一,并且在您开始多年后使用它并意识到您是如何浪费时间的 :-) 如果您不知道,建议您在下一个项目中考虑这一点。

    更新

    Tomas 发表评论后的一些想法。

    确实,对于不太大的项目(特别是如果您对 orm 不是很熟悉,或者您的模型非常复杂),集成供应商 orm 可能是一项很大的工作。 但是经过多年开发任何规模的项目后,我可以说的是,对于任何中等规模的项目,我至少会使用一个定制的、不那么严肃和更灵活的自制 orm,它带有一种通用类,并且少至可能的面向业务的存储库,一个实体知道它的表,可能还有其他相关的表,并且您可以封装一些 sql 或自定义查询函数调用,但围绕该实体(例如实体的主表,图片表关联到该实体,等等)为了向控制器提供数据的单一接口,因此在任何范围内,数据库引擎都独立于模型的 API,并且同样重要的是,控制器不会必须了解任何 DBMS 方面,例如事务的使用,这只是为了确保纯粹与模型相关的行为,并且处于可耻的低级别:与 DBMS 技术需求几乎相关。我的意思是,您的控制器可以知道它正在将内容存储在数据库中,但可以肯定的是,它甚至不必知道事务是什么。

    当然,这是一个哲学讨论,它可能是许多同样有效的观点。

    对于任何自定义 ORM,我建议开始寻找一些 DAO/DTO generator,它可以帮助您从数据库创建主要类,因此您只需在发现异常的地方调整它们以适应您的需求正常的创建-读取-更新-删除的正常行为。这提醒我,你也可以找PHP CRUD,找到一些有用又好玩的工具。

    【讨论】:

    • 你好,卡洛斯!这篇文章非常有趣,尽管我认为对于大多数 PHP 项目来说,做这样的抽象有点过头了,你的应用程序甚至都不知道它使用了一个 db。这是有代价的,而且对于大多数项目来说,它不会得到回报。例如,编写一些更复杂的数据库查询(具有相关子查询、分组依据等)在 SQL 中非常容易,但在某些可重用层中?如此简单的事情可能会变得如此复杂,以至于凡人的开发人员都放弃了复杂但不切实际的方法。我的经验。你好,我现在也碰巧在巴塞罗那! :-)
    • 你好托马斯!我同意你的说法,这就是为什么对于小型项目,我可能不会使用供应商 orm,但可以肯定的是,如果它是中等规模的,我会使用至少一个定制的、不那么严肃和更灵活的 orm,以及一些通用的类,以及尽可能少的面向业务的存储库,其中一个实体知道它的表和其他相关表,并且您可以在其中封装一些 sql,但围绕该实体(例如实体的主表,图片表与该实体相关联,等等)。我将用这个更新答案!顺便说一句,很高兴在城里有 11k 声望的家伙!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-06
    • 1970-01-01
    • 2014-01-11
    • 2012-03-27
    • 1970-01-01
    • 1970-01-01
    • 2016-09-11
    相关资源
    最近更新 更多