如何在PHP OOP中进行数据库事务

时间:2013-12-01 13:26:58

标签: php mysql oop transactions

在我的过时程序代码中(我现在要将其转换为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类客户部门来映射客户部门数据库表,我可以插入表记录如:

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

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

我的客户和部门类在内部使用其他数据库类来查询数据库。

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

我应该在其中实例化Customer和Department类的一个外部类开始/结束事务,还是应该在Customer中以某种方式启动事务(如Customer.startTransaction())并以Department结束(如Department.endTransaction())?还是......

3 个答案:

答案 0 :(得分:2)

其他对象是要走的路。像这样:

$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();

您可以看到save()$customer上不再调用$department方法。 $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");
    }
}

答案 1 :(得分:1)

  

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

除了开始交易之外,您不必做任何事情。

在大多数DBMS接口中,事务是数据库连接的“全局”。如果您启动了一个事务,那么所有后续工作将在该事务的范围内自动完成。如果您提交,则您已提交自上次事务BEGIN以来的所有更改。如果您回滚,则会丢弃自上一个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脚本应该在脚本开头附近启动一个事务,然后再调用任何域模型类,然后在脚本结束时提交或回滚,或者在域模型类完成它们之后工作

答案 2 :(得分:0)

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

为此,肯定你将有一个外部类处理事务,但也可能有其他处理安全性,其他用于构建提供唯一api的查询或数据库引擎,甚至更多,一个读取对象的类为了将它们放在数据库中,所以对象本身甚至不知道它是以数据库结尾。

实现这一目标,将是一项艰苦而漫长的工作,但在此之后,您可以拥有一个自定义且可广泛重复使用的层,使您的项目更具可升级性,更稳定,更可靠。这将是伟大的,你会学到很多东西,然后你会很好地填补。您将拥有某种DBAL或ORM。 但这也不是最好的解决方案,因为有些人已经多年来一直这样做,而且很难实现已有的。

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

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

最着名和最可靠的ORM是

我主要使用doctrine,因为它有一个模块可以与我喜欢的Zend Framework 2集成,但是推动有一些我喜欢的方面。

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

更新

Tomas评论后的一些想法。

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

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

对于任何自定义ORM,我建议您开始寻找一些可以帮助您从数据库中创建主要类的DAO/DTO generator,因此您只需要在找到的位置调整它们以满足您的需求正常create-read-update-delete的正常行为的例外。这提醒我,您也可以查找PHP CRUD并找到一些有用且有趣的工具。