• 结合使用 Oracle Database 11g 和 Python


    结合使用 Oracle Database 11g 和 Python

    本教程介绍如何结合使用 Python 和 Oracle Database 11g。

    所需时间

    大约 1 个小时

    概述

    Python 是一种流行的通用动态脚本语言。随着框架的兴起,Python 也成为 Web 应用程序开发的常用工具。如果您希望结合使用 Python 和 Oracle 数据库,本教程将通过一些示例帮助您快速入门。如果您是初次接触 Python,请参阅 附录:Python 入门,了解这种语言。

    前提条件

    为了学习该动手实践讲座,需要安装以下软件:

    .

    Oracle Database 11gR2,用户名为“pythonhol”,口令(区分大小写)为“welcome”。该模式中的示例表取自 Oracle 的 Human Resources(即“HR”)模式。

    .

    带有 cx_Oracle 5.0.2 扩展的 Python 2.4。

    .

    Django 1.1 框架。

    .

    本教程使用的所有文件都位于您登录的 /home/pythonhol 目录中。

    连接到 Oracle

    要创建到 Oracle 的连接,执行以下步骤:

    .

    查看 $HOME 目录的 connect.py 文件中包含的以下代码。

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    print con.version
    
    con.close()
    

    为了提供用于访问 Oracle 数据库的 API,导入了 cx_Oracle 模块。可以用这种方法在 Python 脚本中包括多个内置的和第三方模块。

    用户名“pythonhol”、口令“welcome”和连接字符串传递给 connect() 方法。在本示例中,使用了 Oracle 的简单连接 (Easy Connect) 连接字符串语法。该字符串由您计算机的 IP 和数据库服务名称“orcl”组成。

    close() 方法关闭连接。脚本结束时,将自动释放所有非显式关闭的连接。

    在命令行终端,运行:

    python connect.py

    如果连接成功,则输出版本号。如果连接失败,则引发异常。

    .

    在 Python 中使用缩进指明代码结构。与许多其他语言不同,没有语句终止符,也不使用 begin/end 关键字或花括号指明代码块。

    在编辑器中打开 connect.py。将输出语句缩进两个空格,保存该文件:

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
      
                    
     print con.version
                  
    con.close()
    

    运行该脚本:

                     
    python connect.py
                  

    用于缩进的空格或 Tab 键的数量并不重要,只要每个代码块中保持一致即可。这个示例中,Python 解释器认为 connect() 调用后面不应有新的代码块级,因此对存在的不同缩进发出警告。

    在其他情况下,例如,在“if”和循环块中(稍后介绍),需要注意使每个块中的所有语句都具有相同的缩进。

    如果您从本教程复制和粘贴代码,请在运行每个示例前检查粘贴的缩进是否正确。

    .

    Python 将一切都当作对象来处理。“con”对象有一个“version”属性,是一个字符串。

    对该脚本进行更改,使其也使用“split”字符串方法:

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
                     
    ver = con.version.split(".")
    print ver
                  
    con.close()
    

    在命令行终端重新运行该脚本:

                     
    python connect.py
                  

    输出是一个“列表”,“列表”是 Python 使用的一种数组的名称。

    .

    可以通过索引访问 Python 列表。

    将 connect.py 更改为:

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    ver = con.version.split(".")
                    
    print ver
    print ver[0]
    print ver[-1]
    print ver[1:4]
                  
    con.close()
    

    在命令行终端重新运行该脚本:

                     
    python connect.py
                  

    Python 列表是以零为基数的,因此 ver[0] 输出该列表的第一个元素。该列表的最后一个元素是 ver[-1]。ver[1:4] 创建了一个列表切片。这将返回从位置 1 开始到位置 4 的元素,但不包括位置 4 的元素。

    .

    Python 列表有一些方法,也可使用运算符来操作列表。

    将 connect.py 更改为:

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    ver = con.version.split(".")
                    
    print ver
    print ver.index("1")
    ver.remove("2")
    print ver
    
    ver1 = ["11", "g"]
    ver2 = ["R", "2"]
    print ver1 + ver2       
    
                  
    con.close()
    

    在命令行终端重新运行该脚本:

                     
    python connect.py
                  

    index("1") 方法返回“1”元素的索引,从零开始计数。remove("2") 方法从列表中删除一个元素。“+”运算符可用于联接两个列表。

    Python 的其他数据类型还包括字典(这是一些关联数组)和名为字节组的类型(类似于列表,但不能更改)。

    可以使用循环来迭代列表。

    将 connect.py 更改为:

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    ver = con.version.split(".")
                    
    for v in ver:
        print v
        if v == "11":
            print "It's 11"
        else:
            print "Not 11"
    
                  
    con.close()
    

    确保缩进正确!

    使用冒号“:”表示代码块。第一个 print 和 if 位于同一个缩进级别,因为它们两个都在循环中。

    在命令行终端重新运行该脚本:

                     
    python connect.py
                  

    该循环依次输出和测试该列表中的每个值。

    使用数据库驻留连接池

    数据库驻留连接池是 Oracle Database 11g 的一个新特性。它对 Web 应用程序常用的短期脚本非常有用。它允许随着 Web 站点吞吐量的增长对连接数量进行扩充。它还支持多台计算机上的多个 Apache 进程共享一个小规模的数据库服务器进程池。没有 DRCP,Python 连接必须启动和终止一个服务器进程。

    下图的左侧是非池化的示意图。每个脚本都有自己的数据库服务器进程。不承担任何数据库工作的脚本一直保留在连接上,直到连接关闭并且服务器终止。下图的右侧是使用了 DRCP 的示意图。所有脚本都可使用来自服务器池的数据库服务器,不再需要时将退回服务器。

    负责长期运行作业的批处理脚本通常使用非池化的连接。

    本教程介绍新应用程序或现有应用程序如何在不编写和更改任何应用程序逻辑的情况下使用 DRCP。执行以下步骤:

    .

    查看 $HOME 目录的 connect_drcp.py 文件中包含的以下代码。

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol', 'welcome', '127.0.0.1:/orcl:pooled', 
                 cclass = "HOL", purity = cx_Oracle.ATTR_PURITY_SELF)
    
    print con.version
    
    con.close()
    

    该脚本与 connect.py 非常类似,但连接字符串后面添加了“:pooled”。还向 connect() 方法中传递了一个连接类“HOL”,并且将该连接的“purity”定义为 ATTR_PURITY_SELF 常量。

    该连接类告诉数据库服务器池这些连接是相关的。不同连接调用之间的会话信息(如默认的数据格式)可能会保留,以改善系统性能。如果之后其他应用程序通过自己的连接类名重用某个池化服务器,则会话信息将作废。

    那些永不应该共享会话信息的应用程序,应使用其他连接类和/或使用 ATTR_PURITY_NEW 强制创建新会话。这虽然降低了整体可伸缩性,但避免了应用程序误用会话信息。

    运行 connect_drcp.py

    python connect_drcp.py
    

    输出仍旧只是数据库版本。

    无需更改脚本逻辑即可从使用 DRCP 连接池获得好处。

    创建简单查询

    开发 Web 应用程序时的一个常见任务是,查询一个数据库然后在 Web 浏览器中显示结果。您可以使用许多函数查询一个 Oracle 数据库,但查询的基础始终是相同的:

    1. 分析要执行的语句。 
    2. 绑定数据值(可选)。 
    3. 执行语句。 
    4. 从数据库中获取结果。

    要创建一个简单查询并显示结果,执行以下步骤。

    .

    查看 $HOME 目录的 query.py 文件中包含的以下代码。

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    
    cur = con.cursor()
    cur.execute('select * from departments order by department_id')
    for result in cur:
        print result
    
    cur.close()
    con.close()         
    

    cursor() 方法打开语句要使用的游标。

    execute() 方法分析并执行语句。

    循环从游标获取每一行并输出该行。

    在终端窗口运行该脚本:

    python query.py
    

    查询结果显示为 Python“字节组”,“字节组”是一些不能更改的数组。

    获取数据

    从 Oracle 数据库中获取数据的方式有多种。执行以下步骤。

    .

    查看 $HOME 目录的 query_one.py 文件中包含的以下代码。

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.01/orcl')
    
    cur = con.cursor()
    cur.execute('select * from departments order by department_id')
                   
    row = cur.fetchone()
    print row
    row = cur.fetchone()
    print row
                
    cur.close()
    con.close()
           
    

    该脚本使用 fetchone() 方法只返回一行作为一个字节组。多次调用该方法后,返回连续的多行:

    在终端窗口运行该脚本:

    python query_one.py

    两次 fetchone() 调用输出两条记录。

    .

    查看 $HOME 目录的 query_many.py 文件中包含的以下代码。

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    
    cur = con.cursor()
    cur.execute('select * from departments order by department_id')
                   
    res = cur.fetchmany(numRows=3)
    print res
    
    cur.close()
                  
    con.close()

    fetchmany() 方法返回一个字节组列表。其中的 numRows 参数指定应返回三行。

    在终端窗口运行该脚本:

    python query_many.py

    以字节组列表形式返回了表的头三行。

    查看 $HOME 目录的 query_all.py 文件中包含的以下代码。

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    
    cur = con.cursor()
    cur.execute('select * from departments order by department_id')
                   
    res = cur.fetchall()
    print res
                
    cur.close()
    con.close()
    

    在终端窗口运行该脚本:

    python query_all.py

    该脚本使用 fetchall() 方法返回所有行。输出仍然是一个字节组列表(Python 使用的一种数组的名称)。每个字节组包含一行的数据。

    .

    可以用任何 Python 方式操作该列表。编辑 query_all.py,将代码进行如下更改(黑体部分),然后再次运行该脚本。

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    
    cur = con.cursor()
    cur.execute('select * from departments order by department_id')
                   
    res = cur.fetchall()
    for r in res:
                    
    print r
    cur.close()
    con.close()

    现在,每个字节组分别输出。选择使用哪种获取方法主要取决于您希望如何处理返回的数据。

    提高查询性能

    本节将演示通过增加每批从 Oracle 返回到 Python 程序的行数来提高查询性能的方法。执行以下步骤:

    .

    首先,创建一个包含许多行的表。查看下面的 query_arraysize.sql 脚本。

    set echo on
    drop table bigtab;
    create table bigtab (mycol varchar2(20));
    begin
    for i in 1..20000
    loop
    insert into bigtab (mycol) values (dbms_random.string('A',20));
    end loop;
    end;

    /

    show errors
    commit;

    在终端窗口中,使用 SQL*Plus 运行该脚本:

                   
    sqlplus pythonhol/welcome@127.0.0.1/orcl
                    
    @query_arraysize exit

    .

    查看 $HOME 目录的 query_arraysize.py 文件中包含的以下代码。

    import time
    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    
    start = time.time()
    
    cur = con.cursor()
    cur.arraysize = 100
    cur.execute('select * from bigtab')
    res = cur.fetchall()
    # print res  # uncomment to display the query results
    
    elapsed = (time.time() - start)
    print elapsed, " seconds"
    
    cur.close()
    con.close()
    

    该脚本使用“time”模块测量查询所花的时间。默认值被设置为 100。这导致每次从数据库向 Python 的缓存返回 100 条记录。这就减少了对数据库的“忘返”次数,通常还会降低网络负载并减少数据库服务器上下文切换次数。从数据库请求更多的数据之前,fetchone()、fetchmany()、甚至 fetchall() 方法都将从缓存读取数据。

    在终端窗口中,运行:

                   
    python query_arraysize.py
                

    重新加载几次,看一下平均时间。

    .

    编辑 query_arraysize.py,将 arraysize 从

    cur.arraysize = 100
    

    更改为

    cur.arraysize = 2000
    

    重新再运行几次脚本,比较这两种 arraysize 设置的性能。通常,较大的数组大小可提高性能。根据您系统的速度,为了明显提高查询速度,您可能需要使用与这里给出的 arraysize 不同的设置值。

    python query_arraysize.py 
    

    cx_Oracle 使用的默认 arraysize 为 50。要增加 arraysize,需要在时间/空间方面进行权衡。arraysizes 越大,Python 中用于缓存记录需要的内存也越大。 

    使用绑定变量

    绑定变量允许您使用新值重新执行语句,避免了重新分析语句的开销。绑定变量提高了代码可重用性,降低了 SQL 注入攻击的风险。

    要在本示例中使用绑定变量,执行以下步骤。

    .

    查看 $HOME 目录的 bind_query.py 文件中包含的以下代码。

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    
    cur = con.cursor()
    cur.prepare('select * from departments where department_id = :id')
    
    cur.execute(None, {'id': 210})
    res = cur.fetchall()
    print res
    
    cur.execute(None, {'id': 110})
    res = cur.fetchall()
    print res
    
    cur.close()
    con.close()       
    

    该语句包含一个绑定变量“:id”。该语句只准备了一次,但针对 WHERE 子句的不同值执行了两次。

    因为 prepare() 方法已经对该语句进行了设置,因此对 execute() 使用特殊符号“None”代替该语句的文本参数。execute() 调用的第二个参数是 Python 字典。在第一个 execute 调用中,该关联数组的“id”关键字的值为 210。

    第一个 execute 使用值 210 进行查询。第二个 execute 使用值 110。

    在终端窗口中,运行:

    python bind_query.py
    

    输出显示了两个部门的详细信息。

    .

    cx_Oracle 驱动程序支持 INSERT 语句的数组绑定,这样可以大大提高单行插入的性能。

    查看下面用于创建要插入数据的表的命令:

    sqlplus pythonhol/welcome@127.0.0.1/orcl
    drop table mytab;
                  
    create table mytab (id number, data varchar2(20)); exit

    运行 SQL*Plus,剪切并粘贴命令。

    .

    查看 $HOME 目录的 bind_insert.py 文件中包含的以下代码。

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    
    rows = [ (1, "First" ),
             (2, "Second" ),
             (3, "Third" ),
             (4, "Fourth" ),
             (5, "Fifth" ),
             (6, "Sixth" ),
             (7, "Seventh" ) ]
    
    cur = con.cursor()
    cur.bindarraysize = 7
    cur.setinputsizes(int, 20)
    cur.executemany("insert into mytab(id, data) values (:1, :2)", rows)
    
    #con.commit()
    
    # Now query the results back
    
    cur2 = con.cursor()
    cur2.execute('select * from mytab')
    res = cur2.fetchall()
    print res
    
                   
    cur.close()
    cur2.close()
    con.close()
                

    “rows”数组包含要插入的数据。

    其中的 bindarraysize 设置为 7,这意味着一步就插入全部七行。setinputsizes() 调用描述了列的情况。第一列是整数。第二列最多为 20 个字节。

    executemany() 调用插入全部七行。

    commit() 调用被注释掉了,因此不会执行。

    脚本的最后一部分是查询返回的结果,并将其显示为一个字节组列表。

    在终端窗口中,运行:

                   
    python bind_insert.py
                

    在脚本的最后自动回滚新结果,因此重新运行该脚本将在表中始终显示相同的行数。

    创建事务

    在 Oracle 数据库中操作数据(插入、更新或删除数据)时,更改的数据或新数据在提交至数据库前仅在数据库会话中可用。更改的数据提交至数据库,然后可供其他用户和会话使用。这是一个数据库事务。执行以下步骤:

    .

    编辑上一节中使用的 bind_insert.py 脚本,取消对 commit 调用的注释(下面的黑体字部分):

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    
    rows = [ (1, "First" ),
             (2, "Second" ),
             (3, "Third" ),
             (4, "Fourth" ),
             (5, "Fifth" ),
             (6, "Sixth" ),
             (7, "Seventh" ) ]
    
    cur = con.cursor()
    cur.bindarraysize = 7
    cur.setinputsizes(int, 20)
    cur.executemany("insert into mytab(id, data) values (:1, :2)", rows)
    
    con.commit()
    
    # Now query the results back
    
    cur2 = con.cursor()
    cur2.execute('select * from mytab')
    res = cur2.fetchall()
    print res
    
                   
    cur.close()
    cur2.close()
    con.close()
                

    commit() 是针对连接执行,而非针对游标。

    重新运行该脚本几次,看一下每次运行在表中增加的行数:

                   
    python bind_insert.py
                

    如果您需要在脚本中启动回滚,则可使用 con.rollback() 方法。

    通常,您希望提交所有数据或者不提交任何数据。进行您自己的事务控制具有性能和数据完整性优势。

    使用 PL/SQL 存储函数和过程

    PL/SQL 是 Oracle 对 SQL 的过程语言扩展。PL/SQL 过程和函数在数据库中存储和运行。使用 PL/SQL 允许所有数据库应用程序重用逻辑,无论应用程序以何种方式访问数据库。许多与数据相关的操作在 PL/SQL 中的执行速度比将数据提取到一个程序中(例如,Python)然后再进行处理的速度快。Oracle 还支持 Java 存储过程。

    在本教程中,您将创建一个 PL/SQL 存储函数和过程并在 Python 脚本中调用它们。执行以下步骤: 

    .

    启动 SQL*Plus,用以下命令创建一个新表 ptab:

    sqlplus pythonhol/welcome@127.0.0.1/orcl
                  
    create table ptab (mydata varchar(20), myid number); exit

    .

    查看 create_func.sql 脚本,它创建一个 PL/SQL 存储函数 myfunc(),以便向 ptab 表中插入一行并且返回插入的值两倍:

    set echo on
                  
    create or replace function
    myfunc(d_p in varchar2, i_p in number) return number as
    begin
    insert into ptab (mydata, myid) values (d_p, i_p);
    return (i_p * 2);
    end;
    /
    show errors

    启动 SQL*Plus 并运行该脚本:

    sqlplus pythonhol/welcome@127.0.0.1/orcl
    @create_func
    exit
    

    .

    查看 $HOME 目录的 plsql_func.py 文件中包含的以下代码。

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    
    cur = con.cursor()
    res = cur.callfunc('myfunc', cx_Oracle.NUMBER, ('abc', 2))
    print res
    
    cur.close()
    con.close()               
    

    该脚本使用 callfunc() 执行此函数。常量 cx_oracle.NUMBER 指示返回值是数字。PL/SQL 函数的两个参数作为一个字节组传输并绑定到该函数的参数。

    在终端窗口中,运行:

                   
    python plsql_func.py
                

    输出的是 PL/SQL 函数计算的结果。

    .

    要调用 PL/SQL 过程,使用 cur.callproc() 方法。

    查看 create_proc.sql 脚本,它将创建一个接受两个参数的 PL/SQL 过程 myproc()。第二个参数包含一个 OUT 返回值。

    set echo on
                  
    create or replace procedure
    myproc(v1_p in number, v2_p out number) as
    begin
    v2_p := v1_p * 2;
    end;
    /
    show errors

    启动 SQL*Plus 并运行该脚本:

    sqlplus pythonhol/welcome@127.0.0.1/orcl
    @create_proc
    exit
    

    .

    查看 $HOME 目录的 plsql_proc.py 文件中包含的以下代码。

    import cx_Oracle
    
    con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
    
    cur = con.cursor()
    myvar = cur.var(cx_Oracle.NUMBER)
    cur.callproc('myproc', (123, myvar))
    print myvar.getvalue()
    
    cur.close()
    con.close()
    

    该脚本创建了一个容纳 OUT 参数的数值变量 myvar。使用一个字节组将数字 123 和返回变量名称绑定到这个过程调用参数。

    在终端窗口中,运行:

                   
    python plsql_proc.py
                

    getvalue() 方法显示返回值。

    连续查询通知

    连续查询通知(也称为数据库更改通知)允许应用程序在表更改时(例如,向表中插入行)接收通知。在许多情况下这一功能非常有用,包括中间层缓存无效的情况。缓存可能会容纳一些与表数据有依赖关系的值。如果表发生更改,缓存的值也必须按照新信息进行更新。

    本示例说明了如何在 Python 中处理 DCN 事件。执行以下步骤:

    .

    查看 $HOME 目录的 dcn.py 文件中包含的以下代码。

    import cx_Oracle
    
    def DCNCallback(message): 
        print "Notification:" 
        for tab in message.tables: 
            print "Table:", tab.name
            for row in tab.rows: 
                if row.operation & cx_Oracle.OPCODE_INSERT: 
                    print "INSERT of rowid:", row.rowid
                if row.operation & cx_Oracle.OPCODE_DELETE: 
                    print "DELETE of rowid:", row.rowid
    
    con = cx_Oracle.Connection("pythonhol/welcome@127.0.0.1/orcl",
                               events = True) 
    subscriptionInsDel = con.subscribe(callback = DCNCallback, 
           operations = cx_Oracle.OPCODE_INSERT | cx_Oracle.OPCODE_DELETE, 
           rowids = True) 
    subscriptionInsDel.registerquery('select * from mytab')
    
    raw_input("Hit Enter to conclude this demo
    ") 
    

    该脚本创建一个名为 DCNCallback() 的函数。表发生更改时将调用该函数。“message”参数是一个 cx_Oracle 对象,将包含有关更改的信息。该函数只输出已经发生的各种更改以及受影响的 rowid。

    该脚本的主体位于该函数之下,注意,“con = ...”的缩进级别与“def ...”相同。该主体用“events = True”参数创建数据库连接,这样在表发生更改时允许数据库向 Python 发送事件通知。

    发生 INSERT 或 UPDATE 时,subscribe() 调用在新线程中注册要调用的 DCNCallback()。任何给定的时间只能运行一个 Python 线程。Python 根据需要在不同线程间切换。 rowids = True 参数使 rowid 在该回调中可以被访问。

    registerquery() 调用注册了从 MYTAB 表选择任何内容的查询。对该表的任何更改,无论是UPDATE 还是 DELETE,都将导致调用 DCNCallback()。 MYTAB 表是在本教程的前面部分创建的。

    该脚本的最后是 raw_input() 调用,它会等待用户输入才终止该脚本的运行。

    要运行 DCN 示例,打开两个终端窗口。在第一个窗口中运行:

    python dcn.py
    

    这将输出一条消息,在没有返回提示符的情况下处于等待状态:

    使其继续运行片刻,然后继续下一步。

    .

    在第二个终端窗口中,输入以下命令:

    sqlplus pythonhol/welcome@127.0.0.1/orcl
                  
    insert into mytab (id) values (11);
    commit;

    发生提交时,Python 脚本(切换回第一个终端窗口)将收到通知并输出更改通知消息:

    您系统上的 rowid 将会不同。

    .

    切换到您的 SQL*Plus 终端窗口,执行以下 SQL 命令删除新行:

    delete from mytab where id = 11;
                  
    commit;

    输出一个新通知,此时 Python 终端类似于:

    再体验几个操作。在提交之前,尝试一次 INSERT 操作,之后再进行一次 DELETE 操作。这次显示收到了每个单独操作的通知。 

    .

    完成这些操作后,在 Python 中按 Enter 结束该演示并退出该脚本。

    .

    对 dcn.py 进行扩展,使其在 MYTAB 发生 UPDATE 时也发出通知。

    在 subscribe() 调用中,将 operations 参数更改为

    operations = cx_Oracle.OPCODE_INSERT | cx_Oracle.OPCODE_DELETE
                  
    | cx_Oracle.OPCODE_UPDATE,

    向 DCNCallback 函数添加了一个新的“if”测试:

    if row.operation & cx_Oracle.OPCODE_UPDATE: 
           print "UPDATE of rowid:", row.rowid
    

    dcn.py 脚本现在应如下所示(以黑体表示所作的更改):

    import cx_Oracle
                  

    def DCNCallback(message):
    print "Notification:"
    for tab in message.tables:
    print "Table:", tab.name
    for row in tab.rows:
    if row.operation & cx_Oracle.OPCODE_INSERT:
    print "INSERT of rowid:", row.rowid
    if row.operation & cx_Oracle.OPCODE_DELETE:
    print "DELETE of rowid:", row.rowid
    if row.operation & cx_Oracle.OPCODE_UPDATE: print "UPDATE of rowid:", row.rowid

    con = cx_Oracle.Connection("pythonhol/welcome@127.0.0.1/orcl",
    events = True)
    subscriptionInsDel = con.subscribe(callback = DCNCallback,
    operations = cx_Oracle.OPCODE_INSERT | cx_Oracle.OPCODE_DELETE
    | cx_Oracle.OPCODE_UPDATE
    ,
    rowids = True)
    subscriptionInsDel.registerquery('select * from mytab')

    raw_input("Hit Enter to conclude this demo ")

    在 Python 终端,重启该脚本:

    .

    在 SQL*Plus 终端,创建一行并对其进行更新:

    insert into mytab (id) values (11);
    update mytab set id = 12 where id = 11;
    commit;
    

    应显示新消息。

    .

    完成这些操作后,在 Python 中按 Enter 结束该演示并退出该脚本。

    数据库更改通知是监视表更改的一个高效方法。它还可用于在给定查询所选行的子集发生更改时发出通知。

    使用 Django 框架

    Django 框架是创建 Python Web 应用程序的几个流行 Python 框架之一。

    该练习将创建一个简单的 Django Web 应用程序。首先介绍一个简单报表。

    .

    To start, create a Django project.  From a terminal window, run:
               
      
                  
    django-admin.py startproject myproj
                

    .

    要在这个新项目中创建应用程序,运行:

                   
    cd myproj
                    
    python manage.py startapp myapp ls -l ls -l myapp

    现在, myproj 目录中包含一个主干应用程序:

    __init__.py — 将目录视作一个 Python 程序包 
    __init__.pyc — __init__.py 的编译版本 
    manage.py — 用于管理该应用程序的脚本 
    myapp — 容纳新应用程序文件的目录 
    settings.py — 该项目的配置设置 
    settings.pyc — settings.py 的编译版本 
    urls.py — 使 URL 可以调用 Python 方法

    myapp 目录包含:

    __init__.py — 将目录视作一个 Python 程序包 
    models.py — 映射到每个表的 Python 类 
    tests.py — 用于构造测试套件 
    views.py — 用于生成 Web 输出的 Python 代码

    要使应用程序连接到数据库,编辑 myproj/settings.py。在靠近该文件顶部的地方,更新要读取的数据库连接参数:

     DATABASE_ENGINE = '
                  
    oracle'
     DATABASE_NAME = '
                  
    127.0.0.1/orcl'
     DATABASE_USER = '
                  
    pythonhol'
     DATABASE_PASSWORD = '
                  
    welcome'
                

    DATABASE_HOST 和 DATABASE_PORT 的值可以保留为空。

    .

    在该文件的底部,向 INSTALLED_APPS 添加一行代码以将应用程序与项目关联:

     INSTALLED_APPS = (
               'django.contrib.auth',
               'django.contrib.contenttypes',
               'django.contrib.sessions',
               'django.contrib.sites',
                
                  
    'myproj.myapp'
               )
                

    保存文件并关闭编辑器。

    .

    运行主干应用程序。在终端窗口中,在 myproj 目录下执行以下命令:

    python manage.py runserver
    

    这将在端口 8000 上启动一个内置的开发 Web 服务器并等待处理 Web 请求。没有返回 shell 提示符。让其运行一会儿。

    .

    打开一个浏览器,输入以下 URL:

    http://127.0.0.1:8000/
    

    将显示默认的 Django 应用程序页面。该阶段尚未建立任何数据库连接。

    .

    要构造应用程序,首先需要编辑 myproj/myapp/models.py 
    添加一个新类。编辑该文件并在末尾处添加以下内容:

    class locations(models.Model):
          location_id = models.IntegerField(primary_key = True)
          street_address = models.CharField(max_length = 40)
          postal_code = models.CharField(max_length = 12)
          city = models.CharField(max_length = 30)
          state_province = models.CharField(max_length = 25)
          country_id = models.CharField(max_length = 2)
          class Meta:
              db_table = "locations"
    

    这将为 LOCATIONS 表创建一个对象关系映射。该类的每个属性对应于表中现有的一列。primary_key = True 这个设置指定了 location_id 列是模型的主键。每个字符字段都指定了自己的长度。

    .

    您需要在 myproj/myapp/views.py 中添加一个新函数来查询该模型并生成报表输出。将以下内容添加到该文件的结尾:

    from django.template import Context, loader
    from django.http import HttpResponse
    from myproj.myapp.models import locations
    
    def index(request):
          location_list = locations.objects.all().order_by('location_id')
          tmpl = loader.get_template("index.html")
          cont = Context({'locations': location_list})
          return HttpResponse(tmpl.render(cont))
    

    这将从 models.py 中导入 'locations' 模型。

    创建了一个按 LOCATION_ID 排序的查询集。使用它呈现 index.html 模板文件。生成的 HTML 页面传递给 Django 并最终显示给用户。

    .

    需要创建 index.html 模板文件,以便在“index”呈现该模型时输出查询集。输入以下命令创建 templates 目录。

    cd myproj/myapp
    mkdir templates
                  
    cd templates

    .

    使用编辑器创建 myproj/myapp/templates/index.html 文件。内容如下:

    <html>
     <head>
      <title>Office Locations</title>
      </head>
    <body bgcolor="#ffffff">
                  
    <h1>Office Locations</h1> <table border="1"> <tr> <th>Location ID</th> <th>Street Address</th> <th>City</th> </tr> {% for loc in locations %} <tr> <td>{{loc.location_id}}</td> <td>{{loc.street_address}}</td> <td>{{loc.city}}</td> </tr> {% endfor %} </table>
    </body> </html>

    这使用了由“{%”和“%}”对及“{{”和“}}”对表示的 Django 模板化语法。locations 变量是由视图中的 Context 调用设置的值。循环针对查询集的每行构造了一个 HTML 表行。Django 对 index.html 文件进行预处理,并创建发送给浏览器的最终 HTML。

    .

    最后,您需要告诉 Django 如何调用我们的“index”视图函数。

    编辑 myproj/urls.py,在末尾处添加下面的“url()”行。

                   
    url(r'^myapp/', include('myproj.myapp.urls')),
                

    r'^myapp/' 字符串是一个正则表达式。这将使 URL 包含要传递给 myproj/mypass/urls.py 文件的“myapp/”,以便进一步处理。正则表达式表示“myapp/”必须像下面那样位于 URL 的主机名之后:

     http://.../myapp/
    

    .

    创建一个新文件 myproj/myapp/urls.py,用于分发应用程序 URL。添加以下代码:

    from django.conf.urls.defaults import *
    from myapp.views import index
    
    urlpatterns = patterns('',
               url(r'^$', index),
               )
    

    这将调用 views.py 中的“index”函数。注意,正则表达式不再包含“myapp”,因为“myapp”已经在项目级进行匹配,所以不会传递到该映射文件中。

    .

    在您的终端窗口中,使用 ^C 停止 Web 服务器并使用以下命令重启:

    python manage.py runserver

    .

    在您的浏览器中加载应用程序的 URL:

    http://127.0.0.1:8000/myapp/

    应用程序页面显示 LOCATIONS 表的三个所需字段的数据:

    该应用程序概括如下:

    1. 对 http://.../myapp 的浏览器页面请求调用 Django Web 服务器

    2. Django 的 URL 调度程序通过 myproj/urls.py 文件中的 URL 模式运行,并选择与该 URL 匹配的第一个 URL。接着调用 myproj/myapp/urls.py 调度程序。然后将调用“index”视图,该视图是一个 Python 回调函数。

    3. 该视图使用数据模型获取数据库数据。然后使用 LOCATIONS 表数据呈现“index.html”模板。

    4. “index”视图返回一个用呈现的模板填充的 HttpResponse 对象。

    5. Django 将 HttpResponse 对象呈现到浏览器。

    本示例中使用的视图是为了查询数据。视图也可以创建保存到数据库中的对象。使用 locations 模型在一个视图中插入一个新对象或新行的代码可如下所示:

        new_loc = locations(       
              location_id = 7000,
              street_address = "123 ABC",
              postal_code = "9999",
              city = "My City",
              state_province = "My State",
              country_id = "US")
        new_loc.save()
    

    如下所示进行删除操作:

        loc = locations.objects.get(location_id__exact=1000)
        loc.delete()
    

    这也说明了生成一个匹配单条记录的查询的语法。

    结合使用 AJAX 和 Python

    本节介绍如何使用 AJAX 技术在不重新加载整个页面的情况下更改部分 HTML 页面。本示例将更改“index”视图,以便在单击一条记录时显示该位置的完整地址。执行以下步骤:

    .

    在第一部分中,将创建一个返回给定位置的完整地址(包括国家/地区)的新视图。您将从建立底层模型开始。编辑 myproj/myapp/models.py:

    在现有的“locations”模型基础上为 COUNTRIES 表添加一个模型:

    class countries(models.Model):
        country_id = models.CharField(max_length = 2, primary_key = True)
        country_name = models.CharField(max_length = 40)
        region_id = models.IntegerField()
        class Meta:
            db_table = "countries"  
    

    要明确与 LOCATIONS 表的关系,需要更改 locations 模型,将以下内容:

     country_id = models.CharField(max_length = 2)
    

    替换为:

     country = models.ForeignKey(countries)
            
    

    注意不是“country_id”了。Django 自动为外键添加“_id”后缀,并在 COUNTRIES 和 LOCATIONS 表中使用 COUNTRY_ID 作为联接列。

    完整的 models.py 文件(用黑体字表示所作的更改)如下:

    from django.db import models
    
    # Create your models here.
    
                   
    class countries(models.Model):
        country_id = models.CharField(max_length = 2, primary_key = True)
        country_name = models.CharField(max_length = 40)
        region_id = models.IntegerField()
        class Meta:
            db_table = "countries"
                
    class locations(models.Model):
        location_id = models.IntegerField(primary_key = True)
        street_address = models.CharField(max_length = 40)
        postal_code = models.CharField(max_length = 12)
        city = models.CharField(max_length = 30)
        state_province = models.CharField(max_length = 25)
      
                  
       country = models.ForeignKey(countries)
        class Meta:
            db_table = "locations"        
                

    .

    编辑 myproj/myapp/views.py,添加一个新视图方法“address”来构造地址。完整的文件如下:

    # Create your views here.
    
    from django.template import Context, loader
    from django.http import HttpResponse
    from myproj.myapp.models import locations
    
    def index(request):
        location_list = locations.objects.all().order_by('location_id')
        tmpl = loader.get_template("index.html")
        cont = Context({'locations': location_list})
        return HttpResponse(tmpl.render(cont))
    
    def address(request, lid):
        address_list = locations.objects.select_related().filter(location_id=lid)
        s = ""
        for loc in address_list:
            s = '[{"STREET_ADDRESS":"' + loc.street_address + 
                    '","CITY":"' + loc.city + 
                    '","POSTAL_CODE":"' + loc.postal_code + 
                    '","COUNTRY":"' + loc.country.country_name + '"}]'
        return HttpResponse(s)
    

    注意:确保缩进与上面显示的和下面的屏幕截图中的完全一样。

    对于 address 视图,像稍后说明的那样,将从调用该视图的 URL 传递“lid”参数值。

    Django 使用“select_related()”方法实现了 LOCATIONS 和 COUNTRIES 两表之间的内联接,并且将针对给定位置标示符的所有查询结果都检索至 address_list 查询集对象中。Django 还绑定“lid”的值以提高效率和安全性。

    “for”循环是一个用于访问检索到的查询集的首个元素的简便方法。因为 LOCATION_ID 是主键,因此该循环将只执行一次。

    变量“s”是用字符串连接组成的,用于包含行的 JSON 格式版本。JSON 是一种文本格式,通常用作浏览器中的 Javascript 和服务器端脚本之间传输数据的轻型协议。较新版本的 Python 具有 JSON 编码和解码的方法,可以使用这些方法代替此处使用的显式字符串连接。

    与已有的“index”视图不同,该脚本不使用 HTML 模板。HttpResponse() 只是将 JSON 字符串回送给浏览器。

    .

    要使新的“address”视图可调用,编辑 myproj/myapp/urls.py(不是 myproj/urls.py),添加:

                   
    url(r'^addr/(?P<lid>w+)$', address),
                

    还要将下一行:

    from myapp.views import index
    

    更改为

    from myapp.views import index
                  
    , address
                

    完整的文件类似于(用黑体表示所作的更改):

    from django.conf.urls.defaults import *
    from myapp.views import index
                  
    , address
                
    urlpatterns = patterns('',
                          
                  
     url(r'^addr/(?P<lid>w+)$', address),
                           url(r'^$', index),
                           )
                

    这个新规则匹配表单的 URL:

     http://.../myapp/addr/1234
    

    在本示例中,参数“lid”将设置为 1234 并传递到“address”视图中。 

    .

    要测试这个新视图,在浏览器中显式调用它:

    http://127.0.0.1:8000/myapp/addr/1000
    

    .

    现在,可以将“index”视图更改为使用“address”视图以便动态更改显示的页面。

    编辑 myapp/templates/index.html,进行以下更改。稍后将显示完整文件以便复制。

    将表下面添加一个新的 HTML 节:

    <p>
    <div id="outputNode"></div>
    </p>
    

    这样,最初不包含任何要输出的文本。加载该页面时,在下表中什么都看不到。完成该示例后,单击一个链接时将运行 Javascript 代码,并将更新该 outputNode 节的文本内容。这样,浏览器无需刷新整个页面就可以显示其新值。 

    现在,添加一个要单击的链接。将模板文本从:

     <td>{{loc.location_id}}</td>
    
    更改为
               
      
                  
    <td><a href="http://this_link_goes_nowhere/"
            onClick="makeRequest({{loc.location_id}}); return false;">
            {{loc.location_id}}<a></td>
                

    这将使所有位置标示符都变成链接。链接是一个无效的 URL,但没关系,因为从不会调用这个链接。相反,onClick 事件将调用我们将创建的一个新的 Javascript 函数。location_id 值(替换为 Django 的模板扩展)传递给该函数。“return false;”语句避免了 HREF 调用(本示例中)无效 URL 的默认操作。

    最后,在 <head> 标记中,添加发出异步 HTTP 请求的 Javascript 函数:

    <script type="text/javascript">
    
    function makeRequest(id)
                  
    {
    httpRequest = new XMLHttpRequest();
    httpRequest.open('POST', 'http://127.0.0.1:8000/myapp/addr/' + id);
    httpRequest.onreadystatechange = function()
    {
    if (httpRequest.readyState == 4) { // The request is complete
    var JSONtext = httpRequest.responseText;
    var myArray = eval('(' + JSONtext + ')'); // beware security issues
    var txt = "Selected address is:<br>";
    for (var i = 0; i < myArray.length; ++i) {
    txt = txt + myArray[i]["STREET_ADDRESS"] + '<br>'
    + myArray[i]["CITY"] + " " + myArray[i]["POSTAL_CODE"] + '<br>'
    + myArray[i]["COUNTRY"];
    }
    document.getElementById("outputNode").innerHTML = txt;
    }
    }
    httpRequest.send(null);
    }
    </script>
    

    这是单击位置标示符时调用的函数。它将 URL 设置为作为 http://127.0.0.1:8000/myapp/addr/ 加上位置标示符进行调用。

    Javascript 的最后一行:

    httpRequest.send(null);

    发起 HTTP 请求,但在这之前,onreadystatechange 操作被设置为一个匿名函数,以随时准备处理任何返回的数据。

    该请求状态改变时,将异步调用状态更改函数。该函数会检查请求是否完成。如果完成,eval 将把检索的 JSON 字符串转换为一个 Javascript 对象。可以在 Javascript 中直接使用结果数组 myArray。在本示例中,将只执行一次循环,因为 LOCATION_ID 是主键。循环体将地址数据字段连接成为一个字符串。

    这样使用 eval() 会带来安全问题。许多第三方 JSON 分析器对生产系统来说都是比较安全的。

    下面的代码行:

    document.getElementById("outputNode").innerHTML = txt;

    将导致 outputNode div 节的页面内容更改为地址。

    完整的 myproj/myapp/templates/index.html 文件(用黑体表示所作的更改)如下:

    <html>
                  
    <head>
    <title>Office Locations</title>
    <script type="text/javascript">
    function makeRequest(id)
                  
    {
    httpRequest = new XMLHttpRequest();
    httpRequest.open('POST', 'http://127.0.0.1:8000/myapp/addr/' + id);
    httpRequest.onreadystatechange = function()
    {
    if (httpRequest.readyState == 4) { // The request is complete
    var JSONtext = httpRequest.responseText;
    var myArray = eval('(' + JSONtext + ')'); // beware security issues
    var txt = "Selected address is:<br>";
    for (var i = 0; i < myArray.length; ++i) {
    txt = txt + myArray[i]["STREET_ADDRESS"] + '<br>'
    + myArray[i]["CITY"] + " " + myArray[i]["POSTAL_CODE"] + '<br>'
    + myArray[i]["COUNTRY"];
    }
    document.getElementById("outputNode").innerHTML = txt;
    }
    }
    httpRequest.send(null);
    }
                   
    </script>
                  
    </head>
    <body bgcolor="#ffffff">
    <h1>Office Locations</h1>
                  
    <table border="1">
    <tr>
    <th>Location ID</th>
    <th>Street Address</th>
    <th>City</th>
    </tr>
    {% for loc in locations %}
    <tr>
    <td><a href="http://this_link_goes_nowhere/"
    onClick="makeRequest({{loc.location_id}}); return false;">
    {{loc.location_id}}<a></td>
    <td>{{loc.street_address}}</td>
    <td>{{loc.city}}</td>
    </tr>
    {% endfor %}
    </table>
                   
    <p>
                    
    <div id="outputNode"></div>
    </p>
    </body>
                  
    </html>

    .

    现在通过在浏览器中加载索引 URL 运行该应用程序:

     http://127.0.0.1:8000/myapp/
    

    现在,所有位置标示符都变成了链接。

    .

    将鼠标放在任何一个链接上。浏览器底部的状态栏将显示 URL http://this_link_goes_nowhere/

    单击最后一个位置标示符“3200”。不会调用这个无效的 URL。相反,将调用 Javascript 函数,并在表下方显示墨西哥办事处的完整地址(您可能需要向下滚动)。使用 Django 中包括的速度较慢的开发 Web 服务器刷新该表可能需要 1 到 2 秒钟的时间:

    审慎使用时,AJAX 技术可以提高 Web 应用程序的性能和适用性。还可以使用 AJAX 显示或发送数据,从而创建复杂应用程序。 

     

    总结

    在本教程中,您学习了如何:

    • 创建连接
    • 使用数据库驻留连接池
    • 创建简单查询
    • 获取数据
    • 提高查询性能
    • 使用绑定变量
    • 创建事务
    • 使用 PL/SQL 存储函数和过程
    • 使用连续查询通知
    • 使用 Django 框架
    • 结合使用 Python 和 AJAX

    附录:Python 入门

    Python 是一种动态类型的脚本语言。它通常用于运行命令行脚本,但也在 Web 应用程序中使用。

    字符串可以包含在单引号或双引号中:

    'A string constant'
    "another constant"
    

    多行字符串使用三引号语法。

    """
    This is
    your string
    """
    

    变量不需要声明类型:

    count = 1
    ename = 'Arnie'
    

    关联数组被称作“字典”:

    a2 = {'PI':3.1415, 'E':2.7182}
    

    有序数组被称作“列表”:

    a3 = [101, 4, 67]
    

    字节组类似于列表,但是,一旦创建就不能更改。使用圆括号创建字节组:

    a4 = (3, 7, 10)
    

    可以使用 print 语句显示字符串和变量:

    print 'Hello, World!'
    print 'Hi', x
    

    也可实现格式化输出:

    print "There are %d %s" % (v1, v2)
    

    Python 中的一切都是一个对象:以上面给出的列表 a3 的值为例,可以使用 append() 方法向该列表添加一个值。

    a3.append(23)
    

    现在,a3 包含 [101, 4, 67, 23]

    可以通过测试和循环来控制代码流。if/elif/else 语句如下所示:

    if sal > 900000:
        print 'Salary is way too big'
    elif sal > 500000:
        print 'Salary is huge'
    else:
        print 'Salary might be OK'
    

    这也说明了如何使用冒号分割这些子句以及如何缩进每个子代码块。

    传统的循环为:

    for i in range(0, 10):
        print i
    

    这将输出数字 0 到 9。i 的值在每次迭代后递增。

    可以使用“for”命令迭代列表和字节组:

    a5 = ['Aa', 'Bb', 'Cc']
    for v in a5:
        print v
    

    这会依次将 v 设置为列表 a5 的每个元素。

    可能会定义如下所示的函数:

    def myfunc(p1, p2):
        "Function documentation: add two numbers"
        print p1, p2
        return p1 + p2
    

    函数也许返回值,也许不返回值。可以使用以下代码调用该函数:

    v3 = myfunc(1, 3)
    

    在函数定义之后必须出现函数调用。

    函数也是对象,也有属性。可以使用内置的 __doc__ 属性查找函数说明:

    print myfunc.__doc__
    

    可以使用 import 语句将子文件包括在 Python 脚本中。

    import os
    import sys
    

    存在许多预先定义的模块,如 os 和 sys 模块。

    注释要么是一行:

    # a short comment
    

    要么是用三个引号引起来的多行:

    """
    a longer
    comment
    """
  • 相关阅读:
    cmb 命令
    一个Cookie登录的示例
    webApp开发流程
    17-8-26-WebApp总结
    通过安装WordPress来搭建lamp开发环境
    Windows修改保存txt文件的默认字符集
    js在客户端创建js可读xml
    eclipse常用快捷键
    Express -api参考
    安装ubuntu kylin时问题解决
  • 原文地址:https://www.cnblogs.com/heric/p/5804523.html
Copyright © 2020-2023  润新知