結合使用 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\n") 

該腳本創建一個名為 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\n")

在 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
"""


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM