摘要了解如何利用 Oracle XML DBPython 腳本和 PL/SQL 存儲過程構建自己的受 Oracle 數據庫支持的 Python 應用程序
盡管 Python 很快在開發人員之中普及但長久以來 Oracle 數據庫一直是最出色的企業級數據庫采用有效的方式將這兩者結合在一起是比較令人感興趣的主題但這實際上是真正的挑戰因為二者都要付出很多
盡管受到警告但本文並不會對最傑出的 Python 和 Oracle 數據庫特性進行概述而是提供一系列獨立的示例本文借助一個示例讓您了解如何采用互補的方法嘗試將這兩種技術結合使用尤其是本文將指導您利用 PL/SQL 存儲過程(在 Python 腳本中編排其調用)創建 Oracle 支持的 Python 應用程序該應用程序在 Python 和數據庫中實施業務邏輯
正如您將在本文中學習到的即使是輕型的 Oracle 數據庫 g 快捷版 (XE) 也可以得到有效利用作為數據驅動的 Web 應用程序的數據庫後端其前端層使用 Python 構建特別是Oracle 數據庫 XE 支持 Oracle XML DB這是構建 Web 應用程序時通常需要的一組 Oracle 數據庫 XML 技術
示例應用程序
在用戶使用您的應用程序時收集有關用戶執行操作的信息成為一種比較流行的接收用戶反饋的機制通常相對於讓用戶明確表達偏好的任何調查來說並入在線應用程序中的點擊跟蹤工具可以為您提供有關用戶偏好的大量信息
舉一個簡單的例子假設您想從OTN — 新文章 RSS頁面中選取三個最新的 Oracle 技術網 (OTN) 文章標題並將這些鏈接放到您的站點上然後您希望收集有關用戶在您的站點上跟隨這些鏈接中的每個鏈接的次數的信息這就是我們的示例將要做的現在讓我們試著弄清如何實現所有這些功能首先必須決定如何在應用程序層之間分發業務邏輯實際上決定如何在應用程序層之間分發業務邏輯可能是規劃數據庫驅動的應用程序最具挑戰性的部分盡管執行業務邏輯通常有多種方法但是您的工作是找到最有效的方法作為一般的經驗當規劃數據庫驅動的應用程序時您應該認真考慮數據庫中關鍵數據處理邏輯的實現這種方法可以幫助您削減與在 Web 服務器和數據庫之間發送數據相關的網絡開銷並且可以減輕 Web 服務器的負擔
將所有這些理論應用到我們的示例上例如將獲得插入到數據庫中的文章詳細信息的負擔放到在數據庫中創建的存儲過程上這樣 Web 服務器不必再處理與維護數據完整性有關的任務這在實踐中的意義是您不必編寫特定 Python 代碼這些代碼負責跟蹤數據庫中是否存在與其鏈接被點擊的文章有關的記錄如果不存在則插入該記錄然後從OTN — 新文章 RSS頁面中獲取所需的所有詳細信息通過讓數據庫自己跟蹤此類事情您可以獲得具有更高可擴展性且更不易出錯的解決方案在本例中Python 代碼將只負責從 RSS 頁面獲取文章鏈接並在用戶單擊某個文章鏈接時向數據庫發送一條消息
圖 給出了示例組件如何彼此交互以及如何與外部源交互的圖形描述
圖 示例應用程序工作原理的高級視圖
本文的其余部分介紹如何實現此示例應用程序有關如何設置和啟動此示例的簡要描述可以參考示例代碼根目錄下的 readmetxt 文件
准備工作環境
要構建此處討論的示例您需要安裝以下軟件組件(參見 Downloads portlet)並使其在您的系統中正常工作
Apache HTTP Server x
Oracle 數據庫 g 快捷版
Python 或更高版本
mod_python 模塊
cx_Oracle 模塊
有關如何安裝上述組件的詳細說明可以參考另一篇 OTN 文章為 Python Server Pages 和 Oracle 構建快速 Web 開發環境(作者Przemyslaw Piotrowski)
設計基礎數據庫
一般來說最好從設計基礎數據庫開始假設您創建了一個用戶模式並授予其創建和操作模式對象所需的所有權限那麼第一步就是創建基礎表在這種特殊情況下您將需要一個唯一的名為 otn_articles_rss 的表創建該表的方式如下
CREATE TABLE otn_articles_rss (
guid VARCHAR() PRIMARY KEY
title VARCHAR()
pubDate VARCHAR()
link VARCHAR()
clicks INTEGER
);
下一步是設計一個將在 Python 代碼中調用的名為 count_clicks 的存儲過程它更新 otn_articles_rss 表中的數據繼續 count_clicks 過程之前您必須先回答以下問題當 count_clicks 嘗試更新尚未插入到 otn_articles_rss 表中的文章記錄的 clicks 字段時會發生什麼情況呢?假設一個新項目剛剛添加到 RSS 頁面然後指向該項目的鏈接出現在您的站點上當有人單擊該鏈接時系統將從負責處理指向 OTN 文章的鏈接上執行的單擊次數的 Python 代碼中調用 count_clicks PL/SQL 過程顯然處理第一次單擊時在 count_clicks 過程中發出的 UPDATE 語句將失敗因為現在還沒有要更新的行
要適應此類情況您可以在 count_clicks 過程中實現一個 IF 塊如果由於 UPDATE 找不到指定的記錄而將 SQL%NOTFOUND 屬性設置為 TRUE 時該塊會發揮作用在該 IF 塊中只要指定了 guid 和單擊次數您就可以先將一個新行插入到 otn_articles_rss 表中之後您應該提交這些更改以便這些更改立即可用於其他用戶會話這些會話可能也需要更新新插入的文章記錄的 clicks 字段最後您應該更新該記錄設置其 titlepubDate 和 link 字段該邏輯可以作為一個單獨的過程(比如 add_article_details)來實現該過程的創建方式如下
CREATE OR REPLACE PROCEDURE add_article_details (gid VARCHAR clks NUMBER) AS
item XMLType;
heading VARCHAR();
published VARCHAR();
url VARCHAR();
BEGIN
SELECT extract((
)getXML()
//item[contains(guid ||gid||)>])
INTO item FROM DUAL;
SELECT extractValue(item //title)
extractValue(item //pubDate)
extractValue(item //link)
INTO heading published url FROM DUAL;
UPDATE otn_articles_rss SET
title = heading
pubDate = published
link = url
clicks = clicks + clks
WHERE guid = gid;
END;
/
正如您所見該過程接受兩個參數gid 是其鏈接受到單擊的文章的 guidclks 是文章查看總次數的增量在該過程主體中您獲得 RSS 文檔的所需部分作為 XMLType 實例然後提取信息之後該信息將立即用於填充 otn_articles_rss 中與正在處理的 RSS 項目關聯的記錄
借助 add_article_details您可以繼續下一環節按照如下方式創建 count_clicks 過程
CREATE OR REPLACE PROCEDURE count_clicks (gid VARCHAR clks NUMBER) AS
BEGIN
UPDATE otn_articles_rss SET
clicks = clicks + clks
WHERE guid = gid;
IF SQL%NOTFOUND THEN
INSERT INTO otn_articles_rss(guid clicks) VALUES(gid );
COMMIT;
add_article_details (gid clks);
END IF;
COMMIT;
END;
/
事務考慮事項
在上面清單中所示的 count_clicks 存儲過程中注意 COMMIT 的使用要緊跟在 INSERT 語句之後最重要的是之後要調用 add_article_details其執行時間可能較長通過在這個階段提交新插入的文章記錄立即用於其他可能的更新否則要等待 add_article_details 完成
考慮以下示例假設 RSS 頁面剛剛更新並且一個全新的文章鏈接變為可用接下來兩個不同的用戶加載您的頁面並幾乎同時單擊這個新鏈接因此將進行兩個對 count_clicks 的同時調用在本例中首先發生的調用將一條新記錄插入到 otn_articles_rss 表中然後它將調用 add_article_details雖然正在執行 add_article_details但對 count_clicks 的另一個調用可以成功執行更新操作增加總單擊次數但是如果此處忽略了 COMMIT那麼第二個調用將找不到用於更新的行因此嘗試執行另一個插入事實上這將導致不可預測的結果它將導致獨特的違反約束的錯誤並且會丟失將第二次 count_clicks 調用進行的更新
此處最令人感興趣的部分是在 count_clicks 過程主體結尾處執行另一個 COMMIT 操作正如您所猜測的需要在這個階段提交以便從更新的記錄中去除鎖定從而使該記錄立即可用於其他會話執行的更新有些人可能會說這個方法降低了靈活性使客戶端無法根據自己的判斷提交或回滾事務但是在這種特殊的情況下這並不是一個大問題因為無論如何從調用 count_clicks 開始的事務都應該立即提交這是因為當用戶單擊某個文章鏈接以離開您的頁面時始終會調用 count_clicks
構建前端層
既然已經創建了存儲過程並且准備好在應用程序中使用那麼您必須弄清如何從前端層編排在數據庫中實現的所有這些應用程序邏輯片段所執行的整個操作流這就是 Python 派上用場的地方了
我們先來看一個簡單的實現為了開始您必須編寫一些 Python 代碼這些代碼將負責從OTN — 新文章 RSS頁面獲取數據然後您將需要開發一些代碼這些代碼將處理在 Web 頁面中的 OTN 文章鏈接上執行的單擊最後您將需要構建該 Web 頁面本身為此您可能會使用 Python 的一種服務器端技術比如 Python Server Pages (PSP)這使得將 Python 代碼嵌入到 HTML 中成為可能
為了編寫 Python 代碼您可以使用您喜歡的文本編輯器如 vi 或記事本創建一個名為 oraclepersistpy 的文件然後在其中插入以下代碼將該文件保存到 Python 解釋器可以找到的位置
import cx_Oracle
import urllib
import xmldomminidom
def getRSS(addr):
xmldoc = xmldomminidomparseString(urlliburlopen(addr)read())
items = xmldocgetElementsByTagName(item)
return items
def getLatestItems(items num):
latest=[]
inxs = [titleguidpubDatelink]
myitems = [item for index item in enumerate(items) if index < num]
for item in myitems:
latestappend(dict(zip(inxs[itemgetElementsByTagName(inx)[]firstChilddata for inx in inxs])))
return latest
正如您所猜測的上面所示的 getRSS 函數將用來從 RSS 頁面獲取數據並將該數據作為一個 DOM 對象返回getLatestItems 專門用來處理該 DOM 文檔將該文檔轉換為 Python dictionary 對象
在 getLatestItems 函數中注意列表內涵(一個新的 Python 語言特性)的使用它提供了一種出色的方法可顯著簡化數據處理任務的編碼
下一步涉及一些代碼的創建這些代碼將處理在指向 OTN 文章的鏈接上執行的單擊這些鏈接是從OTN — 新文章 RSS頁面中獲取並放置到 Web 頁面上的為此您可以開發另一個自定義 Python 函數(比如說 processClick)每次用戶單擊您 Web 頁面上的 OTN 文章鏈接時都會調用該函數要實現 processClick將以下代碼添加到 oraclepersistpy
def processClick(guid clks = ):
db = nnect(usr pswd /XE)
c = dbcursor()
cexecute(call count_clicks(:guid :clks) {guid:guid clks:clks})
dbclose()
以上代碼提供了實際運行的 cx_Oracle 的一個簡單示例它首先連接到基礎數據庫然後它獲得一個 Cursor 對象之後使用該對象的 execute 方法調用在之前的設計基礎數據庫部分討論的 count_clicks 存儲過程
現在您可以繼續下一環節構建 Web 頁面由於這是僅用於演示的應用程序因此該頁面可能非常簡單只包含從 RSS 頁面獲得的鏈接在 APACHE_HOME/htdocs 目錄中創建一個名為 clicktrackpsp 的文件然後在其中插入以下代碼
<html>
<head>
<meta httpequiv=ContentType content=text/html; charset=UTF>
<title>latest OTN articles</title>
</head>
<body>
<h>Three most latest OTN articles</h>
<%import oraclepersist
url =
doc = oraclepersistgetRSS(url)
articles = oraclepersistgetLatestItems(doc )
for article in articles:
%>
<% import urllib %>
<a href=<%= str(article[link]) %>
onclick = thishref = /dispatcherpsp?url=<%=urllibquote_plus(article[link])+str(&guid=)+urllibquote_plus(article[guid]) %>>
<%=str(article[title]) %>
</a><br/>
<%
%>
</body>
</html>
正如您所見以上文檔包含幾個嵌入的 Python 代碼塊在第一個塊中您從之前按照該部分所述創建的 oraclepersist 模塊調用函數獲得列表的一個實例該列表的項目代表三篇最新的 OTN 文章然後在 for 循環中循環該列表為該列表中存在的每個文章項目生成一個鏈接令人感興趣的是盡管這些鏈接中的每個鏈接都引用相應的 OTN 文章地址但是鏈接的 onclick 處理程序將動態修改鏈接到 dispatcherpsp 頁面的目標該目標需要在 APACHE_HOME/htdocs 目錄中創建將兩個參數(即 guid 和 url)附加到每個動態生成的鏈接向 dispatcherpsp 提供有關正在加載的文章的信息
以下是 dispatcherpsp 的代碼
<html>
<body>
<%
import oraclepersist
import urllib
from mod_python import util
params = utilFieldStorage(req)
oraclepersistprocessClick(urllibunquote_plus(params[guid]value) )
pspredirect(urllibunquote_plus(params[url]value))
%>
</body>
</html>
在以上代碼中借助 FieldStorage 類的幫助訪問了附加到 URL 的參數該類來自 mod_python 網頁上提供的 Mod_python 手冊中描述的 util 模塊然後從我們的 oraclepersist 自定義模塊中調用 processClick 函數將從 URL 中提取的 guid 作為第一個參數傳遞將 (意味著一次單擊)作為第二個參數傳遞最後將您的浏覽器重定向到要加載的文章的位置
現在可以測試這個應用程序了由於您處理的是實時數據因此您必須連接到互聯網建立連接之後將浏覽器指向 因此應該出現一個包含指向 OTN 最新文章的三個鏈接的簡單 Web 頁面如圖 所示
圖 這是加載時的應用程序頁面
單擊任一文章鏈接並查看所發生的情況從用戶的角度您將只看到文章正加載到浏覽器中如圖 所示
圖 當跟隨應用程序頁面上的文章鏈接時用戶只能看到文章本身
負責收集有關單擊信息的代碼將在後台運行為了確保該代碼已經這樣操作您可以連接到基礎數據庫並發出以下查詢
SELECT * FROM otn_articles_rss甚至在完全加載文章文檔之前上述代碼應該輸出一個包含有關正在加載的文章信息的行在 clicks 字段中顯示 隨後對此鏈接進行的每個單擊將使 clicks 字段的值增加
采用 Pythonic 方法
在前面部分中編寫的代碼結構與采用 Pythonic 方法實現的代碼看起來不太相同尤其是您按照一定的順序實現了一組將從在 HTML 中嵌入的代碼調用的函數將一個函數返回的結果用作另一個函數的參數實際上這是采用任何其他腳本語言(比如說 PHP)結構化您的代碼的方式
盡管 Python 的真正功能在於它能夠隱藏令人厭煩的實現詳細信息從而提供一個簡單優美而有效的編碼解決方案字典列表和列表內涵是常用的 Python 內置類型在處理結構化數據時可以顯著簡化您的代碼返回在前面部分中討論的 oraclepersistpy 腳本對其進行升級以便最大程度地利用這些傑出的 Python 語言工具為了避免混淆您可以將修訂保存在一個單獨的名為 oraclepersist_listpy 的文件中
import cx_Oracle
import urllib
import xmldomminidom
url =
inxs = [titleguidpubDatelink]
num =
def getRSS(addr):
xmldoc = xmldomminidomparseString(urlliburlopen(addr)read())
items = xmldocgetElementsByTagName(item)
return items
articles = [dict(zip(inxs[itemgetElementsByTagName(inx)[]firstChilddata for inx in inxs])) for index item in enumerate(getRSS(url)) if index < num]
def processClick(guid clks = ):
db = nnect(usr pswd /XE)
c = dbcursor()
cexecute(call count_clicks(:guid :clks) {guid:guid clks:clks})
dbclose()
從以上代碼可以看出利用列表內涵(一種非常有效的結構化應用程序數據的機制)可以顯著減少代碼總量此外客戶端也不必顯式調用模塊函數因此您現在可以重新編寫按照前面部分所述嵌入在 clicktrackpsp 中的 Python 代碼塊如下所示
<%import oraclepersist_list
for article in oraclepersist_listarticles:
%>
盡管現在它更為簡潔但用戶不需要進行任何更改
但是有人可能會說將 PSP 頁面中的代碼與其後端連接實在不是一個靈活的方法例如將要顯示的鏈接數量以及要使用的 RSS 地址硬編碼到 oraclepersist_listpy 腳本中借助這個新的語法您無法根據需要動態更改這些參數要解決此問題可以將列表內涵封裝在 oraclepersist_listpy 腳本中的某個函數中如下所示
def getLatestItems(num = url = ):
inxs = [titleguidpubDatelink]
return [dict(zip(inxs[itemgetElementsByTagName(inx)[]firstChilddata for inx in inxs])) for index item in enumerate(getRSS(url)) if index < num]
正如您所見以上代碼仍然利用了基於使用列表內涵列表和字典的高級語法從而允許在 clicktrackpsp 頁面中動態更改參數以下代碼片段將闡釋現在如何顯式指定要顯示的文章鏈接數量
<%import oraclepersist_list
for article in oraclepersist_listgetLatestItems():
%>
使用面向對象的方法
盡管 Python 中的面向對象編程 (OOP) 是完全可選的但利用該范例可以最大程度地減少冗余高效地自定義現有代碼與其他現代語言一樣Python 允許您使用類封裝邏輯和數據簡化了數據定義和數據操作
回到在前面部分中討論的 oraclepersist_listpy 腳本將 processClick 函數替換為如下所示的 HandleClick 類
class HandleClick:
def __init__(self usrname=usr password =pswd orcldb=/XE):
selfdbconn = nnect(usrname password orcldb)
def __del__(self):
selfdbconnclose()
def processClick(selfguidclks):
selfdbconncursor()execute(call count_clicks(:guid :clks) {guid:guid clks:clks})
假設您將修訂保存在 oraclepersist_classpy 文件中更新後的 dispatcherpsp 現在可能如下所示
<%
import oraclepersist_class
import urllib
from mod_python import util
params = utilFieldStorage(req)
h = oraclepersist_classHandleClick()
hprocessClick(urllibunquote_plus(params[guid]value) )
pspredirect(urllibunquote_plus(params[url]value))
%>
下面您創建 HandleClick 類的一個實例然後調用它的 processClick 方法正確傳遞參數就像您之前所做的那樣
在此處所討論的 HandleClick 類中特別令人感興趣的是特殊類方法 methods __init__ 和 __del__ 的使用與其他特殊方法一樣您從不直接調用它們相反Python 隱式調用它們以響應在實例生命周期期間發生的某些事件因此在創建實例時調用 __init__ 構造函數在銷毀實例之前調用 __del__ 析構函數
在上面的示例中您在構造函數中連接到數據庫並在析構函數中關閉該連接但在某些情況下采用這些方法實現更多操作可能是非常令人感興趣的例如您可能希望在銷毀實例之前從析構函數中發出 SQL 語句以下代碼片段將闡釋如何重新編寫 HandleClick 類以便從析構函數中而不是從某個顯式調用的類方法中調用 count_clicks 存儲過程
class HandleClick:
def __init__(self usrname=usr password =pswd orcldb=/XE):
selfdbconn = nnect(usrname password orcldb)
selfparams ={}
def __del__(self):
selfdbconncursor()execute(call count_clicks(:guid :clks) selfparams)
selfdbconnclose()
def addArticleClick(selfguidclks):
selfparams[guid]=guid
selfparams[clks]=clks
正如您所見更新的 HandleClick 類中不再有 processClick相反客戶端代碼應調用 addArticleClick該函數用要傳遞給 count_clicks 存儲過程的參數填充該類的屬性 params dictionary將從析構函數中調用 count_clicks 存儲過程因此現在您可以重新編寫嵌入在 dispatcherpsp 頁面中的 Python 代碼塊如下所示
<%
import oraclepersist_class
import urllib
from mod_python import util
params = utilFieldStorage(req)
h = oraclepersist_classHandleClick()
haddArticleClick(urllibunquote_plus(params[guid]value) )
del h
pspredirect(urllibunquote_plus(params[url]value))
%>
注意此處使用 del 語句取消包含綁定對 HandleClick 類的某個實例的引用的 h 變量由於這是對該實例的唯一引用因此之後 Python 將使用一種名為垃圾回收的機制隱式刪除該實例刪除後將自動觸發 __del__ 析構函數執行 SQL 語句然後關閉連接
上面的示例極好地說明了采用 Python 開發面向對象的代碼時使用特殊方法可以獲取的優勢在這個特殊示例中客戶端代碼只負責為要針對數據庫發出的查詢設置參數而 Python 隱式執行其余操作
結論
正如您在本文中所學到的開發一個可擴展的數據庫驅動的 Web 應用程序需要進行較良好的規劃繼續構建應用程序組件和編寫代碼之前您必須首先決定可以在數據庫中實現的應用程序邏輯的數量以及可以在前端層實現的操作
設計文章示例時將一些數據處理邏輯放到數據庫中實現幾個 PL/SQL 存儲過程在這裡您學習了如何使用 Oracle XML DB 特性從網頁中獲取 XML 數據然後從獲取的 XML 文檔中提取所需的信息然後構建一些 Python 代碼用以編排存儲過程所執行的完整操作流依次從構建的 PSP 頁面中調用這些 Python 代碼以實現應用程序的前端層因此您獲得了相應的應用程序該應用程序從網頁中獲取某些實時數據並跟蹤用戶在您站點上的活動將該信息存儲在數據庫中在 Python 端您看到了如何使用 Python 語言的內置工具獲取保留以及操作結構化數據這些工具包括列表字典和列表內涵您還了解了在將應用程序邏輯和數據封裝到類中時如何利用 Python 的面向對象的特性
From:http://tw.wingwit.com/Article/program/Oracle/201311/18497.html