Java 理論與實(shí)踐: 在沒有數(shù)據(jù)庫的情況下進(jìn)行數(shù)據(jù)庫查詢
手里有錘子的時(shí)候,看什么東西都像釘子(就像古諺語所說的那樣)。但是如果沒有錘子時(shí)該怎樣辦呢?有時(shí),您可以去借一把錘子。然后,拿著這把借來的錘子敲打虛擬的釘子,最后歸還錘子,沒人知道這些。在本月的 Java 理論與實(shí)踐 系列中,Brian Goetz 將演示如何將 SQL 或者 XQuery 這樣的數(shù)據(jù)操縱之錘應(yīng)用于非持久存儲(chǔ)的數(shù)據(jù)。請(qǐng)?jiān)诒疚母綆У?討論論壇 中與作者和其他讀者分享您對(duì)本文的看法。(也可以單擊本文頂部或底部的 討論 來訪問該論壇。)
我最近仔細(xì)考察了一個(gè)項(xiàng)目,該項(xiàng)目涉及相當(dāng)多的 Web 快速搜索。當(dāng)爬蟲程序爬過不同的 Web 站點(diǎn)時(shí),它將建立一個(gè)數(shù)據(jù)庫,該數(shù)據(jù)庫中包括它所爬過的站點(diǎn)和網(wǎng)頁、每一頁所包含的鏈接、每一頁的分析結(jié)果等數(shù)據(jù)。最終結(jié)果是一組報(bào)告,詳細(xì)說明經(jīng)過了哪些站點(diǎn)和頁面、哪些是一直鏈接的、哪些鏈接已經(jīng)斷開、哪些頁面有錯(cuò)誤、計(jì)算出的頁面規(guī)格,等等。開始的時(shí)候,沒人確切知道需要什么樣的報(bào)告,或者應(yīng)當(dāng)采用什么樣的格式 —— 只知道有一些內(nèi)容要報(bào)告。這表明報(bào)告開發(fā)階段會(huì)是一個(gè)反復(fù)的階段,要經(jīng)過多次反饋、修改,并且可能嘗試使用不同的結(jié)構(gòu)。惟一確定的報(bào)告要求是,報(bào)告應(yīng)當(dāng)以 XML 形式展示,也可能以 HTML 形式展示。因此,開發(fā)和修改報(bào)告的過程必須是輕量級(jí)的,因?yàn)閳?bào)告要求是“動(dòng)態(tài)發(fā)現(xiàn)”的,而不是預(yù)先指定的。
對(duì)這個(gè)問題的“最顯而易見的”解決方法是將所有東西都放入 SQL 數(shù)據(jù)庫中 —— 頁面、鏈接、度量標(biāo)準(zhǔn)、HTTP 結(jié)果代碼、計(jì)時(shí)結(jié)果和其他元數(shù)據(jù)。這個(gè)問題可以借助關(guān)系表示來很好地解決,特別是因?yàn)檫@種方法不需要存儲(chǔ)已訪問頁面的內(nèi)容,只需要存儲(chǔ)它們的結(jié)構(gòu)和元數(shù)據(jù)。
到目前為止,這個(gè)項(xiàng)目看起來像是一個(gè)典型的數(shù)據(jù)庫應(yīng)用程序,并且它并不缺少可供選擇的持久性策略。但是,或許可以避免使用數(shù)據(jù)庫持久存儲(chǔ)數(shù)據(jù)的復(fù)雜性 —— 這個(gè)快速搜索工具(crawler)只訪問數(shù)萬個(gè)頁面。這個(gè)數(shù)字不是很大,因此可以將整個(gè)數(shù)據(jù)庫放在內(nèi)存中,當(dāng)需要持久存儲(chǔ)數(shù)據(jù)時(shí),可以通過序列化來實(shí)現(xiàn)它。(是的,加載和保存操作要花費(fèi)較長(zhǎng)的時(shí)間,但是這些操作并不經(jīng)常執(zhí)行。)懶惰反而帶來了一個(gè)好處 —— 不需要處理持久性極大地縮短了開發(fā)應(yīng)用程序的時(shí)間,因而顯著地減少了開發(fā)工作量。構(gòu)建和操縱內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)要比每次添加、提取或者分析數(shù)據(jù)時(shí)都使用數(shù)據(jù)庫容易得多。不管選擇了哪種持久存儲(chǔ)模型,都會(huì)限制任何觸及到數(shù)據(jù)的代碼的構(gòu)造。
內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)是一種樹型結(jié)構(gòu),如清單 1 所示,它的根是快速搜索過的各個(gè)網(wǎng)站的主頁,因此 Visitor 模式是搜索這些主頁或者從中提取數(shù)據(jù)的理想模式。(構(gòu)建一個(gè)防止陷入鏈接循環(huán) —— A 鏈接到 B、B 鏈接到 C、C 鏈接到 A —— 的基本 Visitor 類并不是很難。)
清單 1. Web 爬行器的一個(gè)簡(jiǎn)化方案
|
這個(gè)快速搜索工具的應(yīng)用程序中有十多個(gè) Visitor,它們所做的事情類似于選擇頁面做進(jìn)一步分析、選擇不帶鏈接的頁面、列出“被鏈接最多”的頁面,等等。因?yàn)樗羞@些操作都很簡(jiǎn)單,所以 Visitor 模式(如清單 2 所示)可以工作得很好,由于數(shù)據(jù)結(jié)構(gòu)可以放到內(nèi)存中,因此就算進(jìn)行徹底搜索,花費(fèi)也不是很大:
清單 2. 用于 Web 快速搜索工具數(shù)據(jù)庫的 Visitor 模式
|
如果不運(yùn)行報(bào)告的話,Visitor 策略在訪問數(shù)據(jù)方面會(huì)做得非常好。使用數(shù)據(jù)庫進(jìn)行持久存儲(chǔ)的一個(gè)好處是:在生成報(bào)告時(shí),SQL 的能力就會(huì)大放光彩 —— 幾乎可以讓數(shù)據(jù)庫做任何事情。甚至用 SQL 生成報(bào)告原型也很容易 —— 運(yùn)行原型報(bào)告,如果結(jié)果不是所需要的結(jié)果,那么可以修改 SQL 查詢或者編寫新的查詢,然后再試一試。如果改變的只是 SQL 查詢的話,那么這個(gè)編輯-編譯-運(yùn)行周期可能很快。如果 SQL 不是存儲(chǔ)在程序中,那么您甚至可以跳過這個(gè)周期的編譯部分,這樣可以快速生成報(bào)告的原型。確定所需要的報(bào)告后,將它們構(gòu)建到應(yīng)用程序中就很容易了。
因此,雖然對(duì)于添加新結(jié)果、尋找特定的結(jié)果和進(jìn)行特殊傳輸來說,內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)都表現(xiàn)得很不錯(cuò),但是對(duì)于報(bào)告來說,這些變成了不利條件。對(duì)于所有其自身結(jié)構(gòu)與數(shù)據(jù)庫結(jié)構(gòu)不同的報(bào)告,Visitor 都必須創(chuàng)建一個(gè)全新的數(shù)據(jù)結(jié)構(gòu),以包含報(bào)告數(shù)據(jù)。因此,每一種報(bào)告類型都需要有自己的、特定于報(bào)告的中間數(shù)據(jù)結(jié)構(gòu)來存放結(jié)果,還需要一個(gè)用來填充中間數(shù)據(jù)結(jié)構(gòu)的訪問者,以及用來將中間數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換成最終報(bào)告的后處理(post-processing)代碼。似乎需要做很多工作,尤其在大多數(shù)原型報(bào)告將被拋棄時(shí)。例如,假定您想要列出所有從其他網(wǎng)站鏈接到某個(gè)給定網(wǎng)站的頁面的報(bào)告、所有外部頁面的列表報(bào)告,以及站點(diǎn)上鏈接該頁面的那些頁面的列表,然后,根據(jù)鏈接的數(shù)量對(duì)報(bào)告進(jìn)行歸類,鏈接最多的頁面顯示在最前面。這個(gè)計(jì)劃基本上將數(shù)據(jù)結(jié)構(gòu)從里到外翻了個(gè)個(gè)兒。為了用 Visitor 實(shí)現(xiàn)這種數(shù)據(jù)轉(zhuǎn)換,需要獲得從某個(gè)給定網(wǎng)站可以到達(dá)的外部頁面鏈接的列表,并根據(jù)被鏈接的頁面對(duì)它們進(jìn)行分類,如清單 3 所示:
清單 3. Visitor 列出被鏈接最多的頁面,以及鏈接到它們的頁面
|
清單 3 中的 Visitor 生成一個(gè)映射,將每一個(gè)外部頁面與鏈接它的一組內(nèi)部頁面相關(guān)聯(lián)。為了準(zhǔn)備該報(bào)告,還必須根據(jù)關(guān)聯(lián)頁面的大小對(duì)這些條目進(jìn)行分類,然后創(chuàng)建報(bào)告。雖然沒有任何困難步驟,但是每一個(gè)報(bào)告需要的特定于報(bào)告的代碼數(shù)量卻很多,因此快速報(bào)告原型就成為一個(gè)重要的目標(biāo)(因?yàn)闆]有提出報(bào)告要求),試驗(yàn)新報(bào)告的開銷比理想情況更高。許多報(bào)告需要多次傳遞數(shù)據(jù),以便對(duì)數(shù)據(jù)進(jìn)行選擇、匯總和分類。
![]() ![]() |
![]()
|
這時(shí),缺少一個(gè)正式的數(shù)據(jù)模型開始成為一項(xiàng)不利因素,該數(shù)據(jù)模型可以用于描述收集的數(shù)據(jù),并且可以用它更容易地表示選擇和聚合查詢。也許懶惰不像開始希望的那樣有效。但是,雖然這個(gè)應(yīng)用程序缺少正式數(shù)據(jù)模型,但也許我們可以將數(shù)據(jù)存儲(chǔ)到內(nèi)存中的數(shù)據(jù)庫,并憑借該數(shù)據(jù)庫進(jìn)行查詢,通過這種方式借用一個(gè)數(shù)據(jù)模型。有兩種可能會(huì)立即出現(xiàn)在您的腦海中:開源的內(nèi)存中的 SQL 數(shù)據(jù)庫 HSQLDB 和 XQuery。我不需要數(shù)據(jù)庫提供的持久性,但是我確實(shí)需要查詢語言。
HSQLDB 是一個(gè)用 Java 語言編寫的可嵌入的數(shù)據(jù)庫引擎。它既包含適用于內(nèi)存中表的表類型,又包含適用于基于磁盤的表的表類型,設(shè)計(jì)該引擎為了將表完全嵌入到應(yīng)用程序中,消除與大多數(shù)真實(shí)數(shù)據(jù)庫相關(guān)的管理開銷。要將數(shù)據(jù)裝載到 HSQLDB,只需編寫一個(gè) Visitor 即可,該 Visitor 將遍歷內(nèi)存中的數(shù)據(jù)結(jié)構(gòu),并為每一個(gè)將要存儲(chǔ)的實(shí)體生成相應(yīng)的 INSERT 語句。然后可以對(duì)這個(gè)內(nèi)存中的數(shù)據(jù)庫表執(zhí)行 SQL 查詢,以生成報(bào)告,并在完成這些操作后拋棄這個(gè)“數(shù)據(jù)庫”。
HSQLDB 方法是一個(gè)可行方法,但您很快就發(fā)現(xiàn),我必須為對(duì)象關(guān)系的不匹配而兩次(而不是一次)受罰 —— 一次是在將樹型結(jié)構(gòu)數(shù)據(jù)庫轉(zhuǎn)換為關(guān)系數(shù)據(jù)模型時(shí),一次是在將平面關(guān)系查詢結(jié)果轉(zhuǎn)換成結(jié)構(gòu)化的 XML 或者 HTML 結(jié)果集時(shí)。此外,將 JDBC ResultSet 后處理為 DOM 表示形式的 XML 或者 HTML 文檔也不是一項(xiàng)很容易的任務(wù),需要為每一個(gè)報(bào)告提供一些定制的編碼。因此雖然內(nèi)存中的 SQL 數(shù)據(jù)庫 的確 可以簡(jiǎn)化查詢,但是從數(shù)據(jù)庫中存入和取出數(shù)據(jù)所需要的額外代碼會(huì)抵消所有節(jié)省的代碼。
![]() ![]() |
![]()
|
另一個(gè)容易得到的數(shù)據(jù)查詢方法是 XQuery。XQuery 的優(yōu)點(diǎn)是,它是為生成 XML 或者 HTML 文檔作為查詢結(jié)果而設(shè)計(jì)的,因此不需要對(duì)查詢結(jié)果進(jìn)行后處理。這種想法很有吸引力 —— 每個(gè)報(bào)告只有一層編碼,而不是兩層或者更多層。因此第一項(xiàng)任務(wù)是構(gòu)建一個(gè)表示整個(gè)數(shù)據(jù)集的 XML 文檔。設(shè)計(jì)一個(gè)簡(jiǎn)單的 XML 數(shù)據(jù)模型和編寫遍歷數(shù)據(jù)結(jié)構(gòu),并將每一個(gè)元素附加到一個(gè) DOM 文檔中的 Visitor 很簡(jiǎn)單。(不需要寫出這個(gè)文檔??梢詫⑺3衷趦?nèi)存中,用于查詢,然后在完成查詢時(shí)丟棄它。當(dāng)?shù)讓訑?shù)據(jù)改變時(shí),可以重新生成它。)之后,所有要做的就是編寫 XQuery 查詢,該查詢將選擇并聚集用于報(bào)告的數(shù)據(jù),并按最終需要的格式(XML 或 HTML)對(duì)它們進(jìn)行格式化。查詢可以存儲(chǔ)在單獨(dú)的文件中,以便進(jìn)行快速原型制造,因此,可支持多種報(bào)告格式。使用 Saxon 評(píng)估查詢的代碼如清單 4 中所示:
清單 4. 執(zhí)行 XQuery 查詢并將結(jié)果序列化為 XML 或 HTML 文檔的代碼
|
表示數(shù)據(jù)庫的 XML 文檔的結(jié)構(gòu)與內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)稍有不同,每一個(gè) <site> 元素都有嵌套的 <page> 元素,每一個(gè) <page> 元素都有嵌套的 <link> 元素,而每一個(gè) <link> 元素都有 <link-to> 和 <link-from> 元素。實(shí)踐證明,這種表示方法對(duì)于大多數(shù)報(bào)告都很方便。
清單 5 顯示了一個(gè)示例 XQuery 報(bào)告,這個(gè)報(bào)告處理鏈接的選擇、分類和表示。它有幾個(gè)地方優(yōu)于 Visitor 方法 —— 不僅代碼少(因?yàn)椴樵冋Z言支持選擇、聚積和分類),而且所有報(bào)告的代碼 —— 選擇、聚積、分類和表示 —— 都在一個(gè)位置上。
清單 5.生成鏈接次數(shù)最多的頁面的完整報(bào)告的 XQuery 代碼
|
![]() ![]() |
![]()
|
從開發(fā)成本角度看,XQuery 方法已證實(shí)可以節(jié)約大量成本。樹型結(jié)構(gòu)對(duì)于構(gòu)建和搜索數(shù)據(jù)很理想,但對(duì)于報(bào)告,就不是很理想了。XML 方法很適合于報(bào)告(因?yàn)榭梢岳?XQuery 的能力),但是對(duì)于整個(gè)應(yīng)用程序的實(shí)現(xiàn),該方法還有很多不便,并會(huì)降低性能。因?yàn)閿?shù)據(jù)集的大小是可管理的 —— 只有幾十兆字節(jié),所以可以將數(shù)據(jù)從一種格式轉(zhuǎn)換為從開發(fā)的角度看最方便的另一種格式。更大的數(shù)據(jù)集,比如不能完全存儲(chǔ)到內(nèi)存中的數(shù)據(jù)集,會(huì)要求整個(gè)應(yīng)用程序都圍繞著一個(gè)數(shù)據(jù)庫構(gòu)建。雖然有許多處理數(shù)據(jù)持久性的好工具,但是它們需要的工作都比簡(jiǎn)單操縱內(nèi)存中數(shù)據(jù)結(jié)構(gòu)要多得多。如果數(shù)據(jù)集的大小合適,那么就可以同時(shí)利用這兩種方法的長(zhǎng)處。
posted on 2006-08-24 17:36 Binary 閱讀(324) 評(píng)論(0) 編輯 收藏 所屬分類: j2se