文/圖 sislcb
這篇文章介紹我在使用Delphi7+SQLite3過程中遇到的問題,然後以一個完整的實例說明SQLite在Delphi中的使用方法。
起因
我看到《承影隨時學英語2.0》這個軟件,覺得很不錯,於是下載下來看了一下,發現使用了SQLite數據庫,利用的是ODBC連接方式,於是我也有了自己寫一個類似程序的想法,畢竟這是別人的東西,自己寫的話,用起來感覺就不一樣了,說干就干。
SQLite介紹
SQLite是一個老牌的輕量級別的本地文件數據庫,完全免費且開源,不需要安裝,無須任何配置,當然,這樣管理功能就不是很強大了,但是它的主要應用也是在本地數據庫,應該說是最簡單好用的嵌入式本地數據庫了吧。象FireBird雖然也提供了嵌入式數據庫,但是它自身附帶的DLL太多,看起來沒那麼清爽,而SQLite只要一個DLL就可以實現全部功能。SQLite不需要數據庫引擎,只有一個數據文件,占用系統資源非常少,很適合做Demo或小型應用。同時,SQLite也是關系型數據庫,支持大部分SQL語句,這是它比BDB(Berkely DB)優秀的地方,當然性能跟它比還是有差距的。它支持事務機制和blob數據類型,支持大部分SQL92標准,最大支持數據庫到2T。它還有Python、Tcl、PHP、Java的綁定,這些語言可以直接使用SQLite數據庫,因為它們自身包含了支持;還有ODBC接口,非常方便使用。
SQLite的一些基本操作跟SQL很類似,基本上有SQL基礎的都能看明白。SQLite的圖像查看工具有很多,比如SQLiteSpy、SQLiteBrowser等。我這裡使用的是SQLiteSpy。
SQLite默認是utf8編碼,使用pragma encoding可以看出數據庫的編碼。建立數據庫後,可以直接輸入“pragma encoding = UTF8/UTF16”來改變編碼,但數據庫有了數據以後,編碼是不可以修改的。
SQLite的源碼可以http://www.sqlite.org獲得。關於SQLite的更進一步的語法和信息,請參http://www.sqlite.com.cn/
Delphi的SQLite組件介紹
Delphi是一個很優秀的開發工具,雖然它自身沒提供對SQLite的支持,但已經有人開發出了第三方控件,非常方便。不用直接使用SQLite提供的API,只要你拖下控件,設置下屬性就行了。支持SQLite的控件有兩個:ASQLite和ZEOSDBO,其中ASQLite是專門為SQLite編寫的,ZEOSDBO是支持很多數據庫的,包括Oracle、Firebird等,類似於dbexpress,但應該比它強大。
ASQLite是一個Delphi的開源封裝庫,發布包中有兩個子壓縮包,其中ASQLite(ASQLiteD4和ASQLiteD5)是開發時的調試組件,ASQLitePkg (D4/D5)是發布時的運行組件。ZEOSDBO是基於VCL標准的數據庫接口實現,可以像BDE、ADO、DBX那樣使用這一組控件,它不但可以訪問免費的小型的數據庫,而且MSSQL、DB2、Oracle、Sybase也同樣支持。支持的VCL開發工具有Delphi 5-10、BCB 5/6、Kylix 2/3、Lazarus等。訪問任何數據庫都是統一的控件,只要選擇不同的Protocol就可以了,確實是非常方便和強大的。
程序的設計
有了上面的基礎知識,我們就可以進行程序的設計了,首先必須明確程序的功能。軟件的功能需求有:1)基本功能:背誦單詞/句子;2)控制功能:播放/暫停/設置播放時間;3)設置窗體屬性:置頂/透明;4)雜項:歷史/退出。
有了需求後,就是怎麼設計的問題了。首先是數據庫的設計,打開SQLiteSpy,點擊“New DataBase”,彈出對話框後,輸入你想保存的數據庫名字,點保存;接著在SQLiteSpy右上的空白處輸入如下代碼:
CREATE TABLE English900
(
[explain] varchar(256),
word varchar(256),
xuhao integer
);
CREATE TABLE EnglishWord
(
xuhao integer,
[explain] varchar(256),
word varchar(256)
);
explain是SQLite的關鍵字,所以需要加上括號!之後就可以看到建立好的表了,建立了表之後,我們就需要把數據導進來了。而要把已經存在的數據導入,需要把有數據的表也打開。SQLite只有一個Main數據庫,所以需要用“Attach Database”命令把已經存有數據的數據庫附加到現在的數據庫。這時候的界面如圖1所示。這樣就打開了兩個數據庫,接著是導入命令:
insert into english900(fanyi,juzi,xuhao) select * from [englishdb].english900;
insert into englishword(xuhao,[explain],word) select * from [englishdb].englishword;
就可以把englishdb裡面兩個表的內容導入到現在的Main數據庫表中。好了,數據庫部分就完成了。
繼續軟件的設計,我的思路是這樣的。先使用ASQLite或者ZEOSDBO把數據Select出來,接著放到內存表裡面,准備使用ClientDataSet,然後把ASQLite或者ZEOSDBO關掉,這樣就不會一直保存著連接了。而且把數據保存在內存裡,不用每次拿一個單詞或者句子出來,都要跑到數據庫裡面去取,速度應該相對來說快一點了。
接著是顯示問題,我不想按照數據庫的順序取出來,而是隨機取,這樣隨機的算法怎麼設計呢?第一個笨辦法是在ClientDataSet裡面動態增加一個字段,用來判斷該單詞/句子是否已經被讀取過了,讀取過一次後,就對該字段置為True。但這個辦法很不好,假如隨機到的單詞/句子都是讀取過的,那不是要判斷好久,太沒效率了。還好這時候老張(一個同事)給了一個辦法:分段隨機。第一次從1-10開始隨機取一個,然後從10-20,依此類推。我覺得這個方法挺不錯,就采用了。這樣把數據保存在內存表後,使用定時器固定一段時間就把單詞/句子顯示出來。確定了方案後,就開始編碼了。
程序的編碼
由於ASQLite是專門對SQLite進行定制的,所以我估計它在功能等方面應該會好點吧。於是我首先選擇了ASQLite做為數據庫連接組件。使用的控件有“DBConn:TASQLite3DB;”、“ASQLQuery:TASQLite3Query;”、“cdsCache:TclientDataSet;”、“dspCache:TdataSetProvider;”。
1)設置DBConn
var
Dir:String;
begin
//得到程序目錄
Dir := ExtractFilePath(Application.ExeName);
//設置查找目錄,否則會出現找不到db和dll錯誤
DBConn.DefaultDir := Dir;
//加入數據庫不存在,則自己創建
DBConn.Database := DBmyEnglishDB.db;
DBConn.DriverDLL := DLLSQLite3.dll;
DBConn.Connected := True;
end;
以上動態修改DBConn的屬性,假如直接給它的Database賦值,那就定死在那個文件夾了。這裡要注意,由於有DefaultDir屬性,所以設置了之後,在Database等屬性中,就不用加上Dir了,默認就是在這個路徑尋找的。
2)設置ASQLQuery
Connection屬性設置為 DBConn。
3)設置dspCache
DataSet屬性設置為ASQLQuery;。
4)設置cdsCache
ProviderName設置為dspCache;。
這樣,組件就設置完成了,接下來是按鈕事件。背誦單詞按鈕事件處理如下。
if (Sender as TMenuItem).Checked then
Exit
else
begin
(Sender as TMenuItem).Checked := True;
pm_control_sentence.Checked := False;
with ASQLQuery do
begin
if Active then
Close;
SQL.Clear;
SQL.Text := select * from englishword;
Open;
end;
cdsCache.Data := dspCache.Data;
ASQLQuery.Close;
在把數據保存到ClientDataSet後,就把ASQLQuery關閉。背誦句子的事件和這個類似,就不貼出來了。
以上編譯好後,在測試過程中出現問題了。問題出在這一句:“cdsCache.Data := dspCache.Data”。當我來回切換背誦單詞和背誦句子按鈕時,會出現一個錯誤,而且錯誤是定位在ASQLite的GetFieldData裡面,莫非控件自身有bug?後來我又測試了一些方法,不使用clientdataset和datasetprovider,直接從ASQLiteQuery裡面把數據取出來,還是發現有問題,在嘗試幾次獲取數據之後,就會出現這個錯誤。看來控件出問題的可能性很大了,不過裡面的源碼我也不是很看得明白,所以沒辦法修復,只好轉向了ZEOSDBO,希望能找到轉機吧。
於是去sourceforge把ZEOSDBO控件下載下來,把ASQLQuery和DBConn刪除掉,把ZEOSDBO的Zconnection和Zquery放上去,cdsCahe無須修改,把dspCache的Dataset設置為Zquery就可以了。然後是設置Zconnection和Zquery,具體如下。
1)Zconnection
Dataabase屬性設置為:DBmyEnglishDB.db,這個是數據庫文件所在的地方。
Protocol屬性設置為:sqlite-3,這樣就支持SQLite3的數據庫了。
2)Zquery
只需設置Connection的屬性指向ZConnection控件就可以了。
背誦單詞的代碼如下。
if (Sender as TMenuItem).Checked then
Exit
else
begin
(Sender as TMenuItem).Checked := True;
pm_control_sentence.Checked := False;
with ZROQ do
begin
if Active then
Close;
SQL.Clear;
SQL.Text := select * from englishword;
Open;
end;
cdsCache.Data := dspCache.Data;
//本來想給它動態增加一個字段,然後根據這個字段判斷是否已經顯示過了,但感覺這樣有個問題,假如隨機產生的數一直都是顯示過的,這樣判斷起來的話,就非常不爽,而且感覺這種方法太笨了,窮舉……
//cdsCache.FieldDefs.Add(IsShow,ftInteger);
ZROQ.Close; //保存到內存表後,把ZQuery關閉,這樣就不會占用一個連接了。
tmShow.Enabled := True; //設置定時器啟動,每隔一段時間就可以把數據顯示在界面上了。
執行select語句後,卻發現讀出來的數據是亂碼,但不會出現和ASQLite類似的問題了。這至少說明了ASQLite應該是有問題的,看來接下來就是要解決編碼問題了。在網上搜索了一些資料,沒發現類似的解決方法。於是我開始懷疑是數據庫編碼問題,因為默認是ut