前言
為什么要使用數據庫類庫?
初學者 (甚至一些非初學者)常常犯的錯誤,就是在開發應用程序的時候,缺乏對未來的考慮。假如有一天,你的程序需要使用其它的數據庫,難道你還需要重新寫一篇針對于這個版本數據庫的程序嗎?這不是不可能發生的,尤其是當目前的數據庫可能不適合你當前的需要的時候。 但是當你做這一切的時候 ,你會發現絕非你想像的那樣容易:每一個 DBMS 有不同的函數。舉例來說:在 Mysql 中連接數據庫的函數為 mysql_connect(),而在MSSQL卻是 mssql_connect()。重新查看你所有的代碼,然后修改所有操作數據庫的函數和查詢語法,這絕不是一項簡單的工作。程序會經常的被捆綁到了錯誤的數據庫上,以至無法在運行時達到最優性能。數據庫類庫就是這樣產生的。它允許你通過同樣的代碼來操作不同的數據庫。一個設計良好的數據庫類庫可以完美的改變這一切。它允許你通過極小的修改,就能轉接到其他數據庫:無論你要連接什么數據庫管理系統,只要使用同一個的函數,和不同的參數。舉例來說, 在 ADOdb 中,只需簡單地將 $db = NewADOConnection('access ') 修改為$db = NewADOConnection('mysql'),這樣,你就輕松的把你的程序從 Access 數據庫轉移到了 Mysql 數據庫中?,F在網絡上已經有了很多這樣的數據庫類庫,比如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 之前,請確定你是已經正確的使用那些變量。(很多朋友?;ㄌ鄷r間去修正這些顯而易見的錯誤。) 如果連接沒有任何錯誤提示,那么我們現在已經可以在我們的項目中來使用 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,簡單地將查詢指令緩存起來。在你視圖通過緩存來提高你的應用程序的糟糕性能之前,我建議你先試圖去優化你的查詢指令。有時候,一些簡單的索引可以改變一切——有太多的所謂的專業的解決方案,都在使用糟糕的索引。在本文中,你能找到很多這樣的實例?,F在,讓我們來看看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,并且同一結果有不同的方法來實現。你可以在此找到完整的手冊。
原文轉自:http://www.anti-gravitydesign.com