设计基础数据库
一般来说,最好从设计基础数据库开始。假设您创建了一个用户模式并授予其创建和操作模式对象所需的所有权限,那么第一步就是创建基础表。在这种特殊情况下,您将需要一个唯一的名为 otn_articles_rss 的表,创建该表的方式如下:
CREATE TABLE otn_articles_rss (
guid VARCHAR2(100) PRIMARY KEY,
title VARCHAR2(200),
pubDate VARCHAR2(32),
link VARCHAR2(200),
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 字段。最后,您应该更新该记录,设置其 title、pubDate 和 link 字段。该逻辑可以作为一个单独的过程(比如 add_article_details)来实现,该过程的创建方式如下:CREATE OR REPLACE PROCEDURE add_article_details (gid VARCHAR2, clks NUMBER) AS
item XMLType;
heading VARCHAR2(200);
published VARCHAR2(32);
url VARCHAR2(200);
BEGIN
SELECT extract(httpuritype.createuri('http://feeds.delicious.com/v2/rss/OracleTechnologyNetwork/otntecharticle').getXML(),
'//item[contains(guid, "'||gid||'")>0]')
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 是其链接受到单击的文章的 guid。clks 是文章查看总次数的增量。在该过程主体中,您获得 RSS 文档的所需部分作为 XMLType 实例,然后提取信息,之后该信息将立即用于填充 otn_articles_rss 中与正在处理的 RSS 项目关联的记录。
借助 add_article_details,您可以继续下一环节,按照如下方式创建 count_clicks 过程:CREATE OR REPLACE PROCEDURE count_clicks (gid VARCHAR2, clks NUMBER) ASBEGIN
UPDATE otn_articles_rss SET
clicks = clicks + clks
WHERE guid = gid;
IF SQL%NOTFOUND THEN
INSERT INTO otn_articles_rss(guid, clicks) VALUES(gid, 0);
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 或记事本。创建一个名为 oraclepersist.py 的文件,然后在其中插入以下代码,将该文件保存到 Python 解释器可以找到的位置:import cx_Oracleimport urllib2
import xml.dom.minidom
def getRSS(addr):
xmldoc = xml.dom.minidom.parseString(urllib2.urlopen(addr).read())
items = xmldoc.getElementsByTagName('item')
return items
def getLatestItems(items, num):
latest=[]
inxs = ['title','guid','pubDate','link']
myitems = [item for index, item in enumerate(items) if index < num]
for item in myitems:latest.append(dict(zip(inxs,[item.getElementsByTagName(inx)[0].firstChild.data for inx in inxs])))
return latest
正如您所猜测的,上面所示的 getRSS 函数将用来从 RSS 页面获取数据,并将该数据作为一个 DOM 对象返回。getLatestItems 专门用来处理该 DOM 文档,将该文档转换为 Python dictionary 对象。
在 getLatestItems 函数中,注意列表内涵(一个新的 Python 语言特性)的使用,它提供了一种出色的方法,可显著简化数据处理任务的编码。
下一步涉及一些代码的创建,这些代码将处理在指向 OTN 文章的链接上执行的单击,这些链接是从“OTN — 新文章 RSS”页面中获取并放置到 Web 页面上的。为此,您可以开发另一个自定义 Python 函数(比如说 processClick),每次用户单击您 Web 页面上的 OTN 文章链接时都会调用该函数。要实现 processClick,将以下代码添加到 oraclepersist.py:
def processClick(guid, clks = 1):db = cx_Oracle.connect('usr', 'pswd', '127.0.0.1/XE')
c = db.cursor()
c.execute('''call count_clicks(:guid, :clks)''', {'guid':guid, 'clks':clks})
db.close()
以上代码提供了实际运行的 cx_Oracle 的一个简单示例。它首先连接到基础数据库。然后,它获得一个 Cursor 对象,之后使用该对象的 execute 方法调用在之前的“设计基础数据库”部分讨论的 count_clicks 存储过程。
现在,您可以继续下一环节,构建 Web 页面。由于这是仅用于演示的应用程序,因此该页面可能非常简单,只包含从 RSS 页面获得的链接。在 APACHE_HOME/htdocs 目录中,创建一个名为 clicktrack.psp 的文件,然后在其中插入以下代码:
﹤html﹥
﹤head﹥
﹤meta http-equiv="Content-Type" content="text/html; charset=UTF-8"﹥
﹤title﹥latest OTN articles﹤/title﹥
﹤/head﹥
﹤body﹥
﹤h2﹥Three most latest OTN articles﹤/h2﹥
﹤%import oraclepersist
url = 'http://feeds.delicious.com/v2/rss/OracleTechnologyNetwork/otntecharticle'
doc = oraclepersist.getRSS(url)
articles = oraclepersist.getLatestItems(doc, 3)
for article in articles:
%﹥
﹤% import urllib %﹥
﹤a href=﹤%= str(article['link']) %﹥
onclick = "this.href = '/dispatcher.psp?url=﹤%=urllib.quote_plus(article['link'])+str('&guid=')+urllib.quote_plus(article['guid']) %﹥'"﹥
﹤%=str(article['title']) %﹥
﹤/a﹥﹤br/﹥
﹤%
%﹥
﹤/body﹥
﹤/html﹥
正如您所见,以上文档包含几个嵌入的 Python 代码块。在第一个块中,您从之前按照该部分所述创建的 oraclepersist 模块调用函数,获得列表的一个实例,该列表的项目代表三篇最新的 OTN 文章。然后,在 for 循环中循环该列表,为该列表中存在的每个文章项目生成一个链接。令人感兴趣的是,尽管这些链接中的每个链接都引用相应的 OTN 文章地址,但是链接的 onclick 处理程序将动态修改链接到 dispatcher.psp 页面的目标,该目标需要在 APACHE_HOME/htdocs 目录中创建。将两个参数(即 guid 和 url)附加到每个动态生成的链接,向 dispatcher.psp 提供有关正在加载的文章的信息。
以下是 dispatcher.psp 的代码:
﹤html﹥
﹤body﹥
﹤%
import oraclepersist
import urllib
from mod_python import util
params = util.FieldStorage(req)
oraclepersist.processClick(urllib.unquote_plus(params['guid'].value), 1)
psp.redirect(urllib.unquote_plus(params['url'].value))
%﹥
﹤/body﹥
﹤/html﹥
在以上代码中,借助 FieldStorage 类的帮助访问了附加到 URL 的参数,该类来自 mod_python 网页上提供的 Mod_python 手册中描述的 util 模块。然后,从我们的 oraclepersist 自定义模块中调用 processClick 函数,将从 URL 中提取的 guid 作为第一个参数传递,将 1(意味着一次单击)作为第二个参数传递。最后,将您的浏览器重定向到要加载的文章的位置。
现在,可以测试这个应用程序了。由于您处理的是实时数据,因此您必须连接到互联网。建立连接之后,将浏览器指向 http://localhost/clicktrack.psp。因此,应该出现一个包含指向 OTN 最新文章的三个链接的简单 Web 页面。如图 2 所示。
图 2:这是加载时的应用程序页面。
单击任一文章链接并查看所发生的情况。从用户的角度,您将只看到文章正加载到浏览器中,如图 3 所示。
图 3:当跟随应用程序页面上的文章链接时,用户只能看到文章本身。
负责收集有关单击信息的代码将在后台运行。为了确保该代码已经这样操作,您可以连接到基础数据库并发出以下查询:
SELECT * FROM otn_articles_rss;
甚至在完全加载文章文档之前,上述代码应该输出一个包含有关正在加载的文章信息的行,在 clicks 字段中显示 1。随后对此链接进行的每个单击将使 clicks 字段的值增加 1。