本文借助一个示例让您了解如何采用互补的方法尝试将oracle和python结合使用。

虽然Python 很快在开发人员之中遍及,但长久以来 Oracle 数据库一直是最超卓的企业级数据库。选用有用的办法将这两者结合在一起是比较令人感兴趣的主题,但这实践上是真实的应战,因为二者都要支付许多。

虽然遭到正告,但本文并不会对最超卓的 Python 和 Oracle 数据库特性进行概述,而是供给一系列独立的示例。本文凭借一个示例让您了解怎么选用互补的办法测验将这两种技能结合运用。尤其是,本文将辅导您运用 PL/SQL 存储进程(在 Python 脚本中编列其调用)创立 Oracle 支撑的 Python 运用程序,该运用程序在 Python 和数据库中施行业务逻辑。

正如您将在本文中学习到的,即使是轻型的 Oracle 数据库 10g 方便版 (XE) 也能够得到有用运用,作为数据驱动的 Web 运用程序的数据库后端,其前端层运用 Python 构建。特别是,Oracle 数据库 XE 支撑 Oracle XML DB,这是构建 Web 运用程序时一般需求的一组 Oracle 数据库 XML 技能。

示例运用程序

在用户运用您的运用程序时搜集有关用户履行操作的信息成为一种比较盛行的接纳用户反应的机制。一般,相对于让用户清晰表达偏好的任何查询来说,并入在线运用程序中的点击盯梢东西能够为您供给有关用户偏好的很多信息。

举一个简略的比方,假定您想从“OTN — 新文章 RSS”页面中选取三个最新的 Oracle 技能网 (OTN) 文章标题,并将这些链接放到您的站点上。然后,您期望搜集有关用户在您的站点上跟从这些链接中的每个链接的次数的信息。这便是咱们的示例即将做的。现在,让咱们试着澄清怎么完结一切这些功用。首要,有必要决议怎么在运用程序层之间分发业务逻辑。实践上,决议怎么在运用程序层之间分发业务逻辑或许是规划数据库驱动的运用程序最具应战性的部分。虽然履行业务逻辑一般有多种办法,可是您的作业是找到最有用的办法。作为一般的经历,当规划数据库驱动的运用程序时,您应该仔细考虑数据库中要害数据处理逻辑的完结。这种办法能够协助您削减与在 Web 服务器和数据库之间发送数据相关的网络开支,而且能够减轻 Web 服务器的担负。

将一切这些理论运用到咱们的示例上,例如,将取得刺进到数据库中的文章具体信息的担负放到在数据库中创立的存储进程上,这样 Web 服务器不用再处理与保护数据完好性有关的使命。这在实践中的含义是您不用编写特定 Python 代码,这些代码担任盯梢数据库中是否存在与其链接被点击的文章有关的记载,假如不存在,则刺进该记载,然后从“OTN — 新文章 RSS”页面中获取所需的一切具体信息。经过让数据库自己盯梢此类作业,您能够取得具有更高可扩展性且更不易犯错的处理方案。在本例中,Python 代码将只担任从 RSS 页面获取文章链接,并在用户单击某个文章链接时向数据库发送一条音讯。

图 1 给出了示例组件怎么互相交互以及怎么与外部源交互的图形描绘。
运用Oracle数据库完成Python数据耐久(Python操作oracle数据库)  oracle python 数据库 第1张

图 1:示例运用程序作业原理的高档视图。

本文的其他部分介绍怎么完结此示例运用程序。有关怎么设置和发动此示例的扼要描绘,能够参阅示例代码根目录下的 readme.txt 文件。

预备作业环境

要构建此处评论的示例,您需求装置以下软件组件(拜见 Downloads portlet)并使其在您的系统中正常作业:

Apache HTTP Server 2.x

Oracle 数据库 10g 方便版

Python 2.5 或更高版别

mod_python 模块

cx_Oracle 模块

有关怎么装置上述组件的具体阐明,能够参阅另一篇 OTN 文章“为 Python Server Pages 和 Oracle 构建快速 Web 开发环境”(作者:Przemyslaw Piotrowski)。

#p#

规划根底数据库

一般来说,最好从规划根底数据库开端。假定您创立了一个用户形式并颁发其创立和操作形式方针所需的一切权限,那么第一步便是创立根底表。在这种特别状况下,您将需求一个仅有的名为 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) 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, 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,那么第二个调用将找不到用于更新的行,因而测验履行另一个刺进。事实上,这将导致不行猜想的成果。它将导致共同的违背束缚的过错,而且会丢掉将第2次 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_Oracle
  import 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   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 的文件,然后在其间刺进以下代码:

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(  0

正如您所见,以上文档包含几个嵌入的 Python 代码块。在第一个块中,您从之前依照该部分所述创立的 oraclepersist 模块调用函数,取得列表的一个实例,该列表的项目代表三篇最新的 OTN 文章。然后,在 for 循环中循环该列表,为该列表中存在的每个文章项目生成一个链接。令人感兴趣的是,虽然这些链接中的每个链接都引证相应的 OTN 文章地址,可是链接的 onclick 处理程序将动态修正链接到 dispatcher.psp 页面的方针,该方针需求在 APACHE_HOME/htdocs 目录中创立。将两个参数(即 guid 和 url)附加到每个动态生成的链接,向 dispatcher.psp 供给有关正在加载的文章的信息。

以下是 dispatcher.psp 的代码:

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(  1

在以上代码中,凭借 FieldStorage 类的协助访问了附加到 URL 的参数,该类来自 mod_python 网页上供给的 Mod_python 手册中描绘的 util 模块。然后,从咱们的 oraclepersist 自界说模块中调用 processClick 函数,将从 URL 中提取的 guid 作为第一个参数传递,将 1(意味着一次单击)作为第二个参数传递。最终,将您的浏览器重定向到要加载的文章的方位。

现在,能够测验这个运用程序了。因为您处理的是实时数据,因而您有必要衔接到互联网。树立衔接之后,将浏览器指向 http://localhost/clicktrack.psp。因而,应该呈现一个包含指向 OTN 最新文章的三个链接的简略 Web 页面。如图 2 所示。

图 2:这是加载时的运用程序页面。

运用Oracle数据库完成Python数据耐久(Python操作oracle数据库)  oracle python 数据库 第2张

单击任一文章链接并检查所产生的状况。从用户的视点,您将只看到文章正加载到浏览器中,如图 3 所示。

运用Oracle数据库完成Python数据耐久(Python操作oracle数据库)  oracle python 数据库 第3张

图 3:当跟从运用程序页面上的文章链接时,用户只能看到文章自身。

担任搜集有关单击信息的代码将在后台运转。为了保证该代码现已这样操作,您能够衔接到根底数据库并宣布以下查询:

SELECT * FROM otn_articles_rss;

甚至在彻底加载文章文档之前,上述代码应该输出一个包含有关正在加载的文章信息的行,在 clicks 字段中显现 1。随后对此链接进行的每个单击将使 clicks 字段的值添加 1。
#p#

选用Pythonic 办法

在前面部分中编写的代码结构与选用 Pythonic 办法完结的代码看起来不太相同。尤其是,您依照必定的次序完结了一组将从在 HTML 中嵌入的代码调用的函数,将一个函数回来的成果用作另一个函数的参数。实践上,这是选用任何其他脚本言语(比方说 PHP)结构化您的代码的办法。

虽然 Python 的真实功用在于它能够躲藏令人厌烦的完结具体信息,然后供给一个简略、美丽而有用的编码处理方案。字典、列表和列表内在是常用的 Python 内置类型,在处理结构化数据时能够明显简化您的代码。回来在前面部分中评论的 oraclepersist.py 脚本,对其进行晋级,以便最大程度地运用这些超卓的 Python 言语东西。为了防止混杂,您能够将修订保存在一个独自的名为 oraclepersist_list.py 的文件中:

import cx_Oracle
  import urllib2
  import xml.dom.minidom
  url = 'http://feeds.delicious.com/v2/rss/OracleTechnologyNetwork/otntecharticle'
  inxs = ['title','guid','pubDate','link']
  num = 3
  def getRSS(addr):
  xmldoc = xml.dom.minidom.parseString(urllib2.urlopen(addr).read())
  items = xmldoc.getElementsByTagName('item')
  return items
  articles = [dict(zip(inxs,[item.getElementsByTagName(inx)[0].firstChild.data for inx in inxs])) for index, item in enumerate(getRSS(url)) if index   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()

从以上代码能够看出,运用列表内在(一种十分有用的结构化运用程序数据的机制)能够明显削减代码总量。此外,客户端也不用显式调用模块函数。因而,您现在能够从头编写依照前面部分所述嵌入在 clicktrack.psp 中的 Python 代码块,如下所示:

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(  4

虽然现在它更为简练,但用户不需求进行任何更改。

可是,有人或许会说将 PSP 页面中的代码与其后端衔接真实不是一个灵敏的办法。例如,即将显现的链接数量以及要运用的 RSS 地址硬编码到 oraclepersist_list.py 脚本中,凭借这个新的语法,您无法依据需求动态更改这些参数。要处理此问题,能够将列表内在封装在 oraclepersist_list.py 脚本中的某个函数中,如下所示:

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(  5

正如您所见,以上代码依然运用了根据运用列表内在、列表和字典的高档语法,然后答应在 clicktrack.psp 页面中动态更改参数。以下代码片段将阐释现在怎么显式指定要显现的文章链接数量:

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(  6

运用面向方针的办法

虽然 Python 中的面向方针编程 (OOP) 是彻底可选的,但运用该典范能够最大程度地削减冗余,高效地自界说现有代码。与其他现代言语相同,Python 答应您运用类封装逻辑和数据,简化了数据界说和数据操作。

回到在前面部分中评论的 oraclepersist_list.py 脚本,将 processClick 函数替换为如下所示的 HandleClick 类:

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(  7

假定您将修订保存在 oraclepersist_class.py 文件中,更新后的 dispatcher.psp 现在或许如下所示:

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(  8

下面您创立 HandleClick 类的一个实例,然后调用它的 processClick 办法,正确传递参数,就像您之前所做的那样。

在此场所评论的 HandleClick 类中,特别令人感兴趣的是特别类办法 methods __init__ 和 __del__ 的运用。与其他特别办法相同,您从不直接调用它们。相反,Python 隐式调用它们以响应在实例生命周期期间产生的某些事情。因而在创立实例时调用 __init__ 结构函数,在毁掉实例之前调用 __del__ 析构函数。

在上面的示例中,您在结构函数中衔接到数据库并在析构函数中封闭该衔接。但在某些状况下,选用这些办法完结更多操作或许是十分令人感兴趣的。例如,您或许期望在毁掉实例之前从析构函数中宣布 SQL 句子。以下代码片段将阐释怎么从头编写 HandleClick 类,以便从析构函数中而不是从某个显式调用的类办法中调用 count_clicks 存储进程: 

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(  9
'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;
  /0

正如您所见,更新的 HandleClick 类中不再有 processClick。相反,客户端代码应调用 addArticleClick,该函数用要传递给 count_clicks 存储进程的参数填充该类的特点 params dictionary,将从析构函数中调用 count_clicks 存储进程。因而,现在您能够从头编写嵌入在 dispatcher.psp 页面中的 Python 代码块,如下所示:

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(  9
﹤%
import oraclepersist_class
import urllib
from mod_python import util
params = util.FieldStorage(req)
h = oraclepersist_class.HandleClick()
h.addArticleClick(urllib.unquote_plus(params['guid'].value), 1)
del h
psp.redirect(urllib.unquote_plus(params['url'].value))
%﹥
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(  9

留意,此处运用 del 句子撤销包含绑定对 HandleClick 类的某个实例的引证的 h 变量。因为这是对该实例的仅有引证,因而之后 Python 将运用一种名为废物收回的机制隐式删去该实例。删去后,将主动触发 __del__ 析构函数,履行 SQL 句子,然后封闭衔接。

上面的示例极好地阐明晰选用 Python 开发面向方针的代码时运用特别办法能够获取的优势。在这个特别示例中,客户端代码只担任为要针对数据库宣布的查询设置参数,而 Python 隐式履行其他操作。

定论

正如您在本文中所学到的,开发一个可扩展的数据库驱动的 Web 运用程序需求进行较杰出的规划。持续构建运用程序组件和编写代码之前,您有必要首要决议能够在数据库中完结的运用程序逻辑的数量以及能够在前端层完结的操作。

规划文章示例时,将一些数据处理逻辑放到数据库中,完结几个 PL/SQL 存储进程。在这里您学习了怎么运用 Oracle XML DB 特性从网页中获取 XML 数据,然后从获取的 XML 文档中提取所需的信息。然后,构建一些 Python 代码,用以编列存储进程所履行的完好操作流。顺次从构建的 PSP 页面中调用这些 Python 代码,以完结运用程序的前端层。因而,您取得了相应的运用程序,该运用程序从网页中获取某些实时数据,并盯梢用户在您站点上的活动,将该信息存储在数据库中。在 Python 端,您看到了怎么运用 Python 言语的内置东西获取、保存以及操作结构化数据,这些东西包含:列表、字典和列表内在。您还了解了在将运用程序逻辑和数据封装到类中时怎么运用 Python 的面向方针的特性。

【修改引荐】

  1. Java应向Python学习对待缺点的情绪
  2. Ruby、Python不能要挟Java的13个理由
  3. Python反常处理系统简介
转载请说明出处
知优网 » 运用Oracle数据库完成Python数据耐久(Python操作oracle数据库)

发表评论

您需要后才能发表评论