【IT168 文档】在上一篇文章中,我们介绍了PostgreSQL的事务功能的定义,并讲解如何通过PostgreSQL客户端进行事务处理。而在本文中,我们将介绍如何在自己的PHP应用程序中如何使用事务。
一、概述
给我们的PHP应用程序集成PostgreSQL的事务功能并非什么难事,只要在适当的时间启动事务,然后当所有操作都完成的时候提交事务或者回滚就行了。下面我们介绍利用PHP进行事务处理的一般方式。通过这个示例程序,能够帮您熟悉为应用程序集成事务功能的一般过程。
二、创建PostgreSQL类
虽然PostgreSQL为PHP提供了相应的函数库,但是您最好不要直接从自己的脚本中调用这些函数,相反地,您应该将它们封装到一个类中,然后使用这个类与数据库进行交互,其中清单1提供了一个这样的类,它实现了我们所需的一些基本功能。
清单1 PostgreSQL的数据层类(pgsql.class.php)
class pgsql {
private $linkid; // PostgreSQL连接标识符
private $host; // PostgreSQL服务器主机
private $user; // PostgreSQL用户
private $passwd; // PostgreSQL密码
private $db; // Postgresql数据库
private $result; // 查询的结果
private $querycount; // 已执行的查询总数
/* 类构造函数,用来初始化$host、$user、$passwd和$db字段。 */
function __construct($host, $db, $user, $passwd) {
$this->host = $host;
$this->user = $user;
$this->passwd = $passwd;
$this->db = $db;
}
/* 连接Postgresql数据库 */
function connect(){
try{
$this->linkid = @pg_connect("host=$this->host dbname=$this->db
user=$this->user password=$this->passwd");
if (! $this->linkid)
throw new Exception("Could not connect to PostgreSQL server.");
}
catch (Exception $e) {
die($e->getMessage());
}
}
/* 执行数据库查询。 */
function query($query){
try{
$this->result = @pg_query($this->linkid,$query);
if(! $this->result)
throw new Exception("The database query failed.");
}
catch (Exception $e){
echo $e->getMessage();
}
$this->querycount++;
return $this->result;
}
/* 确定受查询所影响的行的总计。 */
function affectedRows(){
$count = @pg_affected_rows($this->linkid);
return $count;
}
/* 确定查询返回的行的总计。 */
function numRows(){
$count = @pg_num_rows($this->result);
return $count;
}
/* 将查询的结果行作为一个对象返回。 */
function fetchObject(){
$row = @pg_fetch_object($this->result);
return $row;
}
/* 将查询的结果行作为一个索引数组返回。 */
function fetchRow(){
$row = @pg_fetch_row($this->result);
return $row;
}
/* 将查询的结果行作为一个关联数组返回。 */
function fetchArray(){
$row = @pg_fetch_array($this->result);
return $row;
}
/* 返回在这个对象的生存期内执行的查询总数。这不是必须的,但是您也许会感兴趣。 */
function numQueries(){
return $this->querycount;
}
}
?>
借助于我们提供的详细注释,清单1中的代码还是很容易理解的。然而,您可能已经注意到了一个问题,那就是异常的输出。为简单起见,我们使用了一个die()语句来输出与数据库服务器连接有关的异常;可是,失败的查询是不会被返回的。这个实现可能无法满足您的特殊需要,但是我们这里只是用于演示而已。
当然,为了演示事务处理功能,我们PostgreSQL类还需另外再添加五个事务相关的方法:Begintransaction()、commit()、rollback()、setsavepoint()和rollbacktosavepoint()。通过阅读上一篇文章对事务知识的介绍,现在读者对于下面代码的理解应该不成问题了。
$this->query('START TRANSACTION');
}
function commit() {
$this->query('COMMIT');
}
function rollback() {
$this->query('ROLLBACK');
}
function setsavepoint($savepointname){
$this->query("SAVEPOINT $savepointname");
}
function rollbacktosavepoint($savepointname){
$this->query("ROLLBACK TO SAVEPOINT $savepointname");
}
因为这些命令通常不会出错,所以我们没有引入异常处理,从而使我们的示例代码更加简洁。
三、pg_query()函数
函数pg_query()的运转稍微有些复杂,如果不全面理解其行为的话,就有可能对我们的事务逻辑造成严重的影响。这主要是pg_query()确定成功和失败的方式所引起的。当pg_query()被调用时,任何成功执行的查询都会返回一个资源标识符。 这看起来是并没什么,但是别忘了:一次成功的查询并不意味着一切搞定。举例来说,假如您执行下列命令:
echo pg_query($query);
根据之前插入的测试信息,这次查询没有更新任何数据,因为通过名字Rob不会显示任何参与者。然而,pg_query()仍然会煞有介事地向您返回一个资源标识符,因为这次查询是有效的。但是这对事务来说却是一个大问题,因为您需要明确地知道是否真输出了预期的内容。为了正确处理该事务,我们需要同时检查查询的正确运行情况和是否有数据受到影响。为此,我们可以使用pg_affected_rows()函数来达此目的。举例来说,我们可以像下面这样来改写代码,以确定该查询是否有效,以及是否有数据受到了影响,如下所示:
$result = pg_query($query);
if ($result AND pg_affected_rows($result) == 1) echo "TRUE";
else echo "FALSE";
这将返回FALSE。这是结合PHP使用PostgreSQL数据库的事务的关键,所以我们将在下面的示例中加以详解。
四、利用PHP进行事务处理
在本例中,我们将重新创建前面的商品交换应用,这一次我们将使用PHP。为了简单起见,我们省略了许多无关的细节,这个页面将显示一个产品,并提示用户向他的购物车添加商品,如下所示:
<strong>ComputerABC</strong><br />
Owner: Jack<br />
Price: $12.99<br />
A book about computer!<br />
<form action="purchase.php" method="post">
<input type="hidden" name="itemid" value="1" />
<br />
<input type="submit" value="Purchase!" />
</form>
</p>
你可想而知,在这个页面中显示的数据可以简单地从participant和trunk表中提取。在浏览器中刷新后,这个页面将变成图1的样子。
图1 典型的产品展览
单击Purchase! 按钮将把用户带至purchase.php脚本。同时,还会传递一个变量,即$_POST['itemid']。通过使用这个变量连同一些假定的检索交易方和商品主键的类方法,我们可以利用PostgreSQL的事务来给数据库添加产品,并对交易双方账户中的现金进行相应的增减处理,如清单2所示。
清单2 借助purchase.php交易商品
session_start();
include "pgsql.class.php";
//使用引用某种用户会话表的虚构类来检索参与方的主键,并将会话id映射到一个特定用户
$participant = new participant();
$buyerid = $participant->getparticipantkey();
//给商品id取一个友好的变量名
$itemid = $_POST['itemid'];
//使用虚构的item类来检索商品的卖家和价格
$item = new item();
$sellerid = $item->getitemowner($itemid);
$price = $item->getprice($itemid);
//实例化pgsql类
$pgsqldb = new pgsql("localhost","company","webuser","secret");
//连接到PostgreSQL数据库
$pgsqldb->connect();
//假设事务操作会完全成功
$transactionsuccess = TRUE;
//启动该事务
$pgsqldb->begintransaction();
//从买方账户扣款
$query = "UPDATE participant SET cash=cash-$price WHERE participantid=$buyerid";
$result = $pgsqldb->query($query);
if (!$result OR $result->affectedrows() != 1)
$transactionsuccess = FALSE;
//向卖家的账户打款
$query = "UPDATE participant SET cash=cash+$price WHERE participantid=$sellerid";
$result = $pgsqldb->query($query);
if (!$result OR $result->affectedrows() != 1)
$transactionsuccess = FALSE;
//更新商品的属主
$query = "UPDATE trunk SET participantid=$buyerid WHERE trunkid=$itemid";
$result = $pgsqldb->query($query);
if (!$result OR $result->affectedrows() != 1)
$transactionsuccess = FALSE;
//如果$transactionstatus为True,则提交该事务
//否则,回滚所做修改
if ($transactionsuccess) {
$pgsqldb->commit();
echo "The swap took place! Congratulations!";
} else {
$pgsqldb->rollback();
echo "There was a problem with the swap! :-(";
}
?>
如您所见,运行事务的每一步骤之后都会对查询的状态和受影响的数据记录进行检查。任何时候,只要发现出现错误,$transactionsuccess就会被设为FALSE,并且在该脚本结束时回滚所有步骤。当然,您可以优化这个脚本,让它以步步为营的方式启动各个查询,使得只有在确定前一个查询已正确执行之后才会再进行下一个查询,当然,这项练习就留给您自己了。
五、使用事务的注意事项
使用事务时,有许多事项需要注意,例如:
1、只有当整个过程都必须全部成功时才有必要使用事务。举例来说,向购物车添加产品是一个关键的过程,但是浏览可用的产品则不是。
2、PostgreSQL允许您回滚数据定义语句,即任何用于创建、修改或者丢弃一个数据库对象的语句,这里所说的数据库对象包括表、索引、触发器、函数、视图,等等。
3、事务不能嵌套。在COMMIT或者ROLLBACK命令之前提交多个START TRANSACTION命令是没有效果的。然而,您却可以使用保存点来取得嵌套事务所提供的功能。
六、结束语
在上一篇文章中,我们介绍了PostgreSQL的事务功能的定义,并讲解如何通过PostgreSQL客户端。读者通过阅读本文,将会学习什么是事务,PostgreSQL是如何实现它们的。在本文中,我们将介绍如何在自己的PHP应用程序中如何使用事务。当为商务过程建模的时候,数据库的事务功能极为有用,因为它能确保信息的完整性,而这些信息则是组织内最有价值的资产之一。当建立数据库驱动的应用程序的时候,谨慎的使用数据库的事务功能将为您带来巨大的好处。