前言
为什么要使用数据库类库?
初学者 (甚至一些非初学者)常常犯的错误,就是在开发应用程序的时候,缺乏对未来的考虑。假如有一天,你的程序需要使用其它的数据库,难道你还需要重新写一篇针对于这个版本数据库的程序吗?这不是不可能发生的,尤其是当目前的数据库可能不适合你当前的需要的时候。 但是当你做这一切的时候 ,你会发现绝非你想像的那样容易:每一个 DBMS 有不同的函数。举例来说:在 Mysql 中连接数据库的函数为 mysql_connect(),而在MSSQL却是 mssql_connect()。重新查看你所有的代码,然后修改所有操作数据库的函数和查询语法,这绝不是一项简单的工作。程序会经常的被捆绑到了错误的数据库上,以至无法在运行时达到最优性能。数据库类库就是这样产生的。它允许你通过同样的代码来操作不同的数据库。一个设计良好的数据库类库可以完美的改变这一切。它允许你通过极小的修改,就能转接到其他数据库:无论你要连接什么数据库管理系统,只要使用同一个的函数,和不同的参数。举例来说, 在 ADOdb 中,只需简单地将 $db = NewADOConnection('access ') 修改为$db = NewADOConnection('mysql'),这样,你就轻松的把你的程序从 Access 数据库转移到了 Mysql 数据库中。现在网络上已经有了很多这样的数据库类库,比如Pear,PHPlib,我也已经在工作中使用它们了,也许你也曾经用过。但本文仅将重点放在我所特别关注的 ADOdb。本文我只简要地作一个介绍,使你能够马上运用它来开发你的下一个项目。以后的文章中我们将会循序渐进的对它进入更深入的了解。
目前,ADOdb 支持的数据库包括 MySQL, PostgreSQL,Interbase,Firebird,Informix,Oracle,MS SQL 7,Foxpro,Access,ADO,Sybase,FrontBase,DB2 和 generic ODBC。
ADOdb 的安装
安装 ADOdb 是一件极期容易的事,相信聪明的你一定不会感到吃力。
首先, 确定你正在运行的 PHP 是 4.0.4 版或更新版。 如果不是,我强列建议你升级!
从 PHP Everywhere 站点下载 .zip 或 .tgz 文件,解压缩到你所选的路径下。
这个路径不应在网页目录(WWWTREE,译者注:如果你的网页是在/www/下,那么,这个目录就不应为/www/here)下!虽然ADOdb的包含文件已经使用了 .inc.php 的扩展名 ,使得服务器即使是在最糟糕的配置下,也不会将这些.inc文件通过明文方式在浏览器中显示出来,但是我们向来不提倡将库函数文件放在网页目录下的行为。然后把下载下来的文件运行:tar -zxvf adodb350.tgz 解压,在Windows下你可以使用一个你喜欢的解压软件来操作,这样,你会得到一个 adodb 的目录其下包括了许多子目录。
测试你的安装
好了,让我们来测试一下你的安装吧。 通过在脚本中添加下列三行代码来测试你的安装是否成功。注意要把代码中的参数修改成你自己的。
include("$adodb_path/adodb.inc.php"); // includes the adodb library
$db = NewADOConnection('$database_type'); // A new connection
$db->Connect("$host", "$user", "$password", "$database_name");
现在你已经拥有一个数据库连接对象 $db 了。 你也可以使用 ADONewConnection 来替换 NewADOConnection —— 这两个是同一函数的不同的名字。 连接的数据库变量 $database_type 需要针对你的实际情况改成你所需要的。可以使用以下列表中的一个(括号内的为描述部分,不要在代码中使用):
access (Microsoft Access/Jet)
ado (Generic ADO, the base for all the other ADO drivers)
ado_access (Microsoft Access/Jet using ADO)
ado_mssql (Microsoft SQL Server using ADO)
db2 (DB2)
vfp (Microsoft Visual FoxPro)
fbsql (FrontBase)
ibase (Interbase 6 or before)
firebird (Firebird)
informix72 (Informix databases before Informix 7.3)
informix (Informix)
maxsql (MySQL with transaction support)
mssql (Microsoft SQL Server 7)
mssqlpo (Portable mssql driver)
mysql (MySQL without transaction support)
mysqlt (MySQL with transaction support, identical to maxmysql)
oci8 (Oracle 8/9)
oci805 (Oracle 8.0.5)
oci8po (Oracle 8/9 portable driver)
odbc (Generic ODBC, the base for all the other ODBC drivers)
odbc_mssql (MSSQL via ODBC)
odbc_oracle (Oracle via ODBC)
oracle (Oracle 7)
postgres (PostgreSQL)
postgres64 (PostgreSQL 6.4)
postgres7 (PostgreSQL 7, currently identical to postgres )
sqlanywhere (Sybase SQL Anywhere)
sybase (Sybase)
如果你的链接代码出现了错误的提示,那么你首先要检查的地方就是在路径或连接的变量上。在你责备 ADOdb 之前,请确定你是已经正确的使用那些变量。(很多朋友常花太多时间去修正这些显而易见的错误。) 如果连接没有任何错误提示,那么我们现在已经可以在我们的项目中来使用 ADodb 了。
通过你的脚本连接到数据库
把上边的代码加入到你的代码前,让我们先退一步。我们最好能把上边的代码用我们自己的方法来封装起来。这样可以使你的程序变得更灵活、更具移植性。如果你直接把上面的代码插入到你的项目的每个文件中,如果将来项目的路径改变了,将会很容易产生错误,如果你的密码改变了,你可能需要对你所有的脚本进行修改,这样将会影响我们使用库函数的初衷。并且,因为你的密码信息是在WEBTREE下的,这将产生隐患。我推荐将密码信息放在一个独立的包含文件中,比如在 ADOdb 安装目录下的某个地方。如果你要在其他服务器上运行你的程序时,你就不能保证目录结构会是相同的,所以,你要确保这个路径是正确的。我建议使用PHP 的自动包含功能来自动地包含这个文件。
include("$adodb_path/db_values.inc.php");
include("$adodb_path/adodb.inc.php");
$db = NewADOConnection('$database_type');
$db->Connect("$host", "$user", "$password", "employees");
如果你也想要使用持久连接, 不是每次创造一个新的连接(这使许多WEB应用程序得到了加速,但是要注意有些数据库是不支持的)。可以使用 PConnect 替换掉 Connect。
文件 db_values.inc.php 是我们的数据库信息文件,内容为(你需要把下面代码中的变量改成你自己的):
<?php
$database_type="mysql";
$host = "localhost"; // 本地数据库
$user = "ian"
$password = "let_me_in"
?>
你可以在 php.ini 配置中设定自动包含我们的配置文件,具体操作可以修改 PHP.ini 的下述行:
; Automatically add files before or after any PHP document.
auto_prepend_file = /usr/local/build/apache/www/tool_lib/defaults.inc
auto_append_file =
文件 defaults.inc 包含了 $adbdb_path 的值:
<?
$adodb_path = "/usr/local/build/apache/www/tool_lib/";
?>
还有其他方式来实现它,但是我发现这种方法在移植时,可以相对地减少复杂度。
从一个数据库中进行选择(SELECT)操作
当同时使用开发良好的库函数,和PHP自身提供的函数时,可以有多种方法来访问数据库。用什么方法,完全取决于你自己的喜好。
这里是一个简单的例子:
$sql = "SELECT surname, age FROM employees";
$rs = &$db->Execute($sql);
if (!$rs) {
print $db->ErrorMsg(); // Displays the error message if no results could be returned
}
else {
while (!$rs->EOF) {
print $rs->fields[0].' '.$rs->fields[1].'<BR>';
// fields[0] is surname, fields[1] is age
$rs->MoveNext(); // Moves to the next row
}
}
在上例中,$rs->fields 是一个包含返回值的数组。数组索引被赋予了初始的数字,你也可以按下面的方法来指定其索引:
$sql = "SELECT surname, age FROM employees";
$db->SetFetchMode(ADODB_FETCH_ASSOC); // Return associative array
$rs = &$db->Execute($sql);
if (!$rs) {
print $db->ErrorMsg(); // Displays the error message if no results could be returned
}
else {
while (!$rs->EOF) {
print $rs->fields['surname']." ".$rs->fields['age']."<BR>";
$rs->MoveNext(); // Moves to the next row
} // end while
} // end else
对结果浏览的另一个可选的方法是将每条记录作为一个对象返回。 ADOdb 有一个 FetchNextObject() 的函数来实现这一功能,指针会自动地移到下一条记录。
$sql = "SELECT surname, age FROM employees";
$db->SetFetchMode(ADODB_FETCH_ASSOC); // Return associative array
$rs = &$db->Execute($sql);
if (!$rs) {
print $db->ErrorMsg(); // Displays the error message if no results could be returned
}
// loop through results
while ($row = $rs->FetchNextObject()) {
// The field names need to be uppercase
print $row->SURNAME." ".$row->AGE."<BR>";
}
插入、更新记录
基本的 INSERT 操作方便、快捷, 拥有与SELECT一样的语法。
$sql = "INSERT INTO employees (surname, age) values ('Clegg','43')";
if (!($db->Execute($sql))) {
print 'Error inserting: '.$db->ErrorMsg().'<BR>';
}
库函数真正的优点,在于它允许你通过相同的语法将记录放入不同的数据库之内,这在以前是绝对不可能的。通常有两种发生的情形。
第一种,引号。所有的引号需要用脱字符(即`符号,键位在Tab键的上边)代替,否则会引起语法错误。但是一些数据库使用一个单引号,另外一些则使用两个单引号。所以,你应当使用 ADOdb 中的 qstr() 而不是 PHP 中的 addslashes()。这样,返回值就将与你所使用的数据库相吻合了。
第二种,日期。许多数据库接受跟他们的日期类型不一致的、不兼容的格式。 ADOdb 有一个 DBDate() 函数,可以将 Unix 的 timestamp, 或 ISO(Y-m-d) 格式转换成任意格式,以此来满足你的数据库的需求。 见下例:
$employee_surname = $db->qstr("d'Angelo");
$arrival_time = $db->DBDate(time());
// The above two functions also add the enclosing quotes, so, $arrival_time, not '$arrival_time'
$sql = "INSERT INTO employee_arrival (arrival_time,surname) values ($arrival_time,$employee_surname)";
if (!($db->Execute($sql))) {
print 'Error inserting: '.$db->ErrorMsg().'<BR>';
}
你可以以完全相同的方式更新数据库,举例来说:
$sql = "UPDATE employees SET age='44' WHERE id='121')";
if (!($db->Execute($sql))) {
print 'Error updating: '.$db->ErrorMsg().'<BR>';
}
以上仅仅是 Adodb 的一些基本操作 —— 下次我们将会关注一些 ADOdb 提供的比较深层次的东西。 如果我已经使你胃口大开,而且你已经不能再等待, 我建议你去PHP Everywhere看一下,这个站点是ADOdb的专业站点,里面有很多有用的帮助信息。
对查询进行缓存
上个月,我们简单地了解了ADOdb中,如何进行SELECT、INSERT和UPDATE的操作。如果你在ADOdb上是个新手,我建议先读一下上个月的那篇文章。 ADOdb 还有很多更高级的特征,这个月,我们就一起来关注其中的部分内容。
数据库时常会成为应用程序效率低下的祸首。尽量减少对数据库的查询,是提高执行效率的方法之一。这,通常可以通过对整页内容进行缓存(有很多种方法来实现。比如,PEAR->Cache),或者,如果你需要做一张动态页面,并且只想让查询指令被缓存,那么,你可以使用ADOdb,简单地将查询指令缓存起来。在你视图通过缓存来提高你的应用程序的糟糕性能之前,我建议你先试图去优化你的查询指令。有时候,一些简单的索引可以改变一切——有太多的所谓的专业的解决方案,都在使用糟糕的索引。在本文中,你能找到很多这样的实例。现在,让我们来看看ADOdb是如何使你能够对数据库的查询结果进行缓存的。在这个实例中,ADOdb把我们的最后的一次查询的结果保存在/var/tmp/adodb_cache这个缓存文件中,并保留10分钟。
include("$adodb_path/db_values.inc.php");
include("$adodb_path/adodb.inc.php");
$db = NewADOConnection('$database_type');
$db->Connect("$host", "$user", "$password", "employees");
$ADODB_CACHE_DIR = "/var/tmp/adodb_cache"; //Directory to store cached files
$sql = "SELECT surname, age FROM employees";
$rs = &$db->CacheExecute(600,$sql); // Executes, and caches the results for 600 seconds
if (!$rs) {
print $db->ErrorMsg(); // Displays the error message if no results could be returned
}
else {
while (!$rs->EOF) {
print $rs->fields[0].' '.$rs->fields[1].'<BR>';
// fields[0] is surname, fields[1] is age
$rs->MoveNext(); // Moves to the next row
} // end while
} // end else
CacheExecute() 函数有两个参数: 第一个参数是缓存文件将被保留的时间,以秒计时;第二个参数是 SQL 声明。 第一个参数是可选择的 (一些开发者或许会认为它应当成为第二个参数) ,如果你没有限定时间,那么,默认值是 3600 秒,也就是1个小时。缓存文件被命名为 adodb_*.cache,你可以在文件系统中安全地将他们删除。你应该定期清楚过期的缓存文件(用UNIX的“crontab万年历”,或者WINDOWS的“计划任务”。译者注:万年历不知有没有译错,我没有UNIX。)。要注意的是,要使用缓存方法,你需要将PHP的参数magic_quotes_runtime设为off(译者注:在php.ini中,将值设为0)。你可以根据需要,在运行时修改它的值:
set_magic_quotes_runtime(0);
只需将上述代码放到你调用数据库的指令之前就可以了。你可以在任何时候,通过调用CacheFlush();来清除缓存。处于对安全的考虑,ADOdb 也建议将PHP的参数register_globals 设为0(在PHP最新版中,这是默认值) 。
更多关于查询结果的信息
开发者通常会对他们的要得到的结果产生一些质疑,最通常的问题是:有多少个记录?ADOdb能够很容易地通过RecordCount()来回答这个问题。 RowCount()是同义函数。
$sql = "SELECT surname, age FROM employees";
$rs = &$db->CacheExecute(600,$sql); // Executes, and caches the results for 600 seconds
print $rs->RecordCount() . " rows returned]"; // Display number of rows returned
或许第二个问题会产生在需量列表上:被返回字段数量是多少?不用担心,ADOdb有FieldCount()。
$sql = "SELECT surname, age FROM employees";
$rs = &$db->CacheExecute(600,$sql); // Executes, and caches the results for 600 seconds
print $rs->FieldCount() . " columns returned]"; // Display number of rows returned
限制结果
上次我们讨论了如何通过使用一个数据库库函数使你的应用程序更简洁,更易于移植。在从MySQL转移到 Informix中 , 我经历了一次痛苦的移植过程。一切都归咎于非ANSII标准的LIMIT子句( 举例来说, 在MySQL中允许下列指令:SELECT name FROM employee LIMIT 15),它是一个非常有用的功能,可在Informix中却不被支持。(在Informix中,相同功能的书写应该是:SELECT FIRST 15 name FROM employee in Informix。)它似乎对你敲响了警钟,要你停止在你的查询中使用非标准SQL的指令,而去认真地学习标准的SQL。幸运的是,ADOdb有一个处理LIMIT的方法:SelectLimit()。
$sql = "SELECT surname, age FROM employees";
$rs = &$db->SelectLimit($sql, 10, 100); // Select 10 rows, starting at row 100
if (!$rs) {
print $db->ErrorMsg(); // Displays the error message if no results could be returned
}
else {
while (!$rs->EOF) {
print $rs->fields[0].' '.$rs->fields[1].'<BR>';
// fields[0] is surname, fields[1] is age
$rs->MoveNext(); // Moves to the next row
} // end while
} // end else
SelectLimit()将SQL声明作为第一参数,第二个是返回的列的数量,最后一个参数是偏移量(返回的第一行)。注意这跟MySQL的指令中LIMIT子句的参数顺序相反。SelectLimit()对于将搜索结果部分地显示在页面上,是十分有用的,通过Previous和Next按钮,可以浏览所有的结果。
不止一次地,我看到的代码将来自数据库的所有查询结果,都用PHP作过滤处理——杀鸡用牛刀! 你完全可以使用 CacheSelectLimit()函数来贮藏这种类型的结果。
处理事务
处理事务是许多应用程序的一个重要的特征。(对于初学者来说,处理事务是你可以提交一打查询操作的过程,而这些查询是都成功了,还是有部分失败了,都需要考虑进去。
经典范例是银行处理。钱从你的帐户转出,然后转入到其他某个人的帐户中。只要其中任意一项失败,那么,这整个过程都必须被认定为失败。不然,钱被划出,而没有进对方的帐户;或者,钱没有划出,对方帐户无端多了一笔小费。)
处理事务可以在代码级上进行机警地管理控制。 常数错误检查被用来判断是否要COMMIT(事务的所有各项都正确,执行正确,结束事务)还是ROLLBACK(事务中有错误,所有改动需要恢复原来状况)。ADOdb 有一些能使处理操作正确进行的有用的函数。下例在某个职员的结余加10,并在另一个上减10,整个过程有两个查询,作为一个完整的事务。
$sql1 = "UPDATE employees SET balance=balance-10 WHERE id=15";
$sql2 = "UPDATE employees SET balance=balance+10 WHERE id=22";
$db->StartTrans();
$db->Execute($sql);
$db->Execute($sql2);
$db->CompleteTrans();
当你浏览老的代码时,可能会看到一个比较老的方法来实现这一切。使用 BeginTrans()、CommitTrans()和RollbackTrans()函数时,需要你自己来处理错误。StartTrans()和 CompleteTrans()将会自动地处理错误,并适当地进行COMMIT或ROLLBACK。当需要的时候,你可以通过FailTrans()来强制执行ROLLBACK。
当然你可能需要知道处理是失败了还是成功了。ADOdb 提供了 HasFailedTrans() 来实现这个功能。 如果出错(或者,你可以将这种情况称作FailTrans()),返回TRUE;如果成功,返回FALSE。
$sql1 = "UPDATE employees SET balance=balance-10 WHERE id=15";
$sql2 = "UPDATE employees SET balance=balance+10 WHERE id=22";
$db->StartTrans();
$db->Execute($sql);
$db->Execute($sql2);
$db->CompleteTrans();
if ($db->HasFailedTrans()) {
// Something went wrong
}
值得注意的是,你的数据库需要支持这些事务函数。 (大多数的数据库是支持的,不过,MySQL InnoDB表支持,可 MySQL MyISAM 表不支持。)
我希望我所做的一切能让你对数据库库函数能有极大的兴趣。还有大量有趣的函数可以用来从数据库表中自动生成HTML,并且同一结果有不同的方法来实现。你可以在此找到完整的手册。