Python 连接和操作 DB2 和 Oracle 数据库
使用 Python 就难免要应对到不同数据库连接的问题,Python 目前也没有 Java 使用 JDBC 瘦客户端驱动那么方便。本人在用 Python 连接 SQL Server 时经常还是会有些问题,此文只着力于如何用 Python 连接 DB2 和 Oracle 数据库。
从本文中我们将会学到
执行后输出的结果为
这和我们在 Python 中使用数据库的标准流程(conn->conn.cursor()->cursor.execute()->cursor.fetchXxx()) 不太一致,DB2 的操作以 ibm_db 模块为中心。ibm_db 的 conn 对象没什么有效的操作函数, 我们可以列出 ibm_db 的函数和属性
然后像上面那样处理结果
然后调用它
输出为
调用存储过程时注意存储过程的名称,参数个数及类型的匹配,不然任何错误都是报
IBM DB2 的 Python 驱动是以二进制的行式提供的,而且非标准方式使用 DB2 数据库,几乎有方法的参数都是 (*args, **kwargs), 如何调用只好看 IBM 的 Application development in Python with ibm_db,或者也可以看源代码 https://github.com/ibmdb/python-ibmdb
输出为
现在试着用 cx_Oracle 来连接数据库
注意:cx_Oracle 连接时要用 service name, 而不是 SID, 下面将会介绍到如何得到 service name,以及通过 SID 构建 DSN 字符串。
错误来了
下面以在 Mac OS 上安装 Oracle Instant Client 为例,可选择安装 Oracle Instant Client Downloads for macOS(Intel x86) 里的 Basic Light Package(ZIP)(大小 35M)。然后解压某个目录下,比如
因此完整的操作 Oracle 数据库的代码是
输出
报错
通过 SID 构造 DSN 字符串
此时的 dsn_str 是
比如操作 DB2
因为实际上扮演连接操作数据库的角色是 Java 的 JDBC, 所以 url 是一个 JDBC 连接字符串,JayDeBeApi 用 url 结合 driver_args 中的参数去调用 Java 的 Driver.getConnection(url, *driver_args) 方法。JayDeBeApi 所实现的是在 Java 与 Python 之间进行类型的转换,以调用相应的 JDBC 函数。
JayDeBeApi 依赖于 JVM,它借助于 JPype1 试图从 JAVA_HOME 或 PATH 环境变量中找到 java 执行程序启动 JVM。如果没有 JAVA_HOME 或 PATH 中找不到 java,则最后因系统而异采用不同的查找定位 JAVA_HOME 的办法,见 _jvmfinder.py,所以这会造成启动 JVM 非常的慢。
找到的 JVM 的路径可以通过 JPype1 代码打印出来
如果定位到 JAVA_HOME, 输出类似如下的路径
参考:
永久链接 https://yanbin.blog/python-connect-db2-oracle-databases/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
从本文中我们将会学到
- Python 围绕着 ibm_db 来操作 DB2
- 以 Python DB-API 2.0 规范来操作 DB2
- cx_Oracle Python 库 + Oracle Instant Client 操作 Oracle
- 用 Oracle 的 SID 还是 Service Name 来连接数据库
- Python 中借助 JayDeBeApi 使用 JDBC 驱动来操作数据库(以 Oracle 为例)
- 从 JayDeBeApi 中我们了解到 JPype 有助于我们在 Python 中调用 Java
Python 操作 DB2
连接 DB2 数据库要简单的多,只要安装 ibm-db 库pip install ibm-db # 或用是 ibm_db假设数据库中创建有一个表 t1, 并有记录
1CREATE TABLE t1(c1 integer, c2 varchar(8));
2INSERT INTO t1(c1, c2) values(1, 'aaa'), (2, 'bbb');立即查询
然后查询记录的代码如下1import ibm_db
2conn = ibm_db.connect("DATABASE=study;HOSTNAME=localhost;PORT=50000;PROTOCOL=TCPIP;UID=dbuser;PWD=your-password;", "", "")
3stmt = ibm_db.exec_immediate(conn, "select * from t1")
4
5result = ibm_db.fetch_both(stmt)
6while result:
7 print(result)
8 result = ibm_db.fetch_both(stmt)执行后输出的结果为
{'C1': 1, 0: 1, 'C2': 'aaa', 1: 'aaa'}both 表示是结果行里既有列名也有索引号。
{'C1': 2, 0: 2, 'C2': 'bbb', 1: 'bbb'}
这和我们在 Python 中使用数据库的标准流程(conn->conn.cursor()->cursor.execute()->cursor.fetchXxx()) 不太一致,DB2 的操作以 ibm_db 模块为中心。ibm_db 的 conn 对象没什么有效的操作函数, 我们可以列出 ibm_db 的函数和属性
1print('\n'.join([name for name in dir(ibm_db) if name.lower()==name and (not name.startswith('__'))]))active立即执行单条语句用 exec_immediate(), 如果要绑定参数来执行就得用到 prepare, bind_param 和 execute 函数
autocommit
bind_param
callproc
check_function_support
client_info
close
column_privileges
columns
commit
conn_error
conn_errormsg
conn_warn
connect
createdb
cursor_type
dropdb
exec_immediate
execute
execute_many
绑定参数查询
1stmt = ibm_db.prepare(conn, "select * from t1 where c1=?")
2ibm_db.bind_param(stmt, 1, 2)
3ibm_db.execute(stmt)
4
5param = (2,)
6ibm_db.execute(stmt, param)然后像上面那样处理结果
调用存储过程
创建一个示例存储过程1CREATE PROCEDURE test_proc(
2 IN empno integer,
3 IN randing integer,
4 OUT name varchar(8))
5LANGUAGE SQL
6BEGIN
7 SELECT 'abc' INTO name FROM sysibm.sysdummy1;
8END然后调用它
1stmt, *_, name = ibm_db.callproc(conn, 'test_proc', (1, 2, ''))输出为
abc返回值为 statement 和所有输入输出参数组成的列表,输入值我们并不用关心,所以用 *_ 去匹配它们。
调用存储过程时注意存储过程的名称,参数个数及类型的匹配,不然任何错误都是报
Exception: Describe Param Failed: [IBM][CLI Driver][DB2/NT64] SQL0440N No authorized routine named "TEST_PROC" of type "PROCEDURE" having compatible arguments was found. SQLSTATE=42884 SQLCODE=-440根本无法明确是什么错误。
IBM DB2 的 Python 驱动是以二进制的行式提供的,而且非标准方式使用 DB2 数据库,几乎有方法的参数都是 (*args, **kwargs), 如何调用只好看 IBM 的 Application development in Python with ibm_db,或者也可以看源代码 https://github.com/ibmdb/python-ibmdb
标准方式使用 DB2
上面的非标准方式操作 DB2 很反常规,幸好这个驱动同时也提供了ibm_db_dbi: Python driver for IBM DB2 and IBM Informix databases that complies to the DB-API 2.0 specification
1import ibm_db
2import ibm_db_dbi
3
4ibm_db_conn = ibm_db.connect("DATABASE=study;HOSTNAME=localhost;UID=dbuser;PWD=your-password;", "", "")
5# 如果是连接本地数据库,可用 ibm_db.connect("study", "dbuser", your-password")
6conn = ibm_db_dbi.Connection(ibm_db_conn)
7
8cursor = conn.cursor()
9cursor.execute("select * from t1")
10for row in cursor.fetchall():
11 print(row)输出为
(1, 'aaa')最后别忘了用
(2, 'bbb')
conn.close() 关闭连接。Python 操作 Oracle 数据库
Python 连接 Oracle 数据库就比较复杂一些了,除了安装 cx_Oracle 库pip install cx-oracle麻烦的事还在后头
现在试着用 cx_Oracle 来连接数据库
1import cx_Oracle
2
3conn = cx_Oracle.connect('system', 'oracle', '192.168.86.10:1521/EE.oracle.docker')注意:cx_Oracle 连接时要用 service name, 而不是 SID, 下面将会介绍到如何得到 service name,以及通过 SID 构建 DSN 字符串。
错误来了
cx_Oracle.DatabaseError: DPI-1047: Cannot locate a 64-bit Oracle Client library: "dlopen(libclntsh.dylib, 1): image not found". See https://cx-oracle.readthedocs.io/en/latest/user_guide/installation.html for help因为这不是一个瘦客户端,需要安装 Oracle 的本地库来支援该驱动,即 Oracle Instant Client。还可用预先安装了 Oracle Instant Client 的 Docker 镜像 或 Dockerfile。
下面以在 Mac OS 上安装 Oracle Instant Client 为例,可选择安装 Oracle Instant Client Downloads for macOS(Intel x86) 里的 Basic Light Package(ZIP)(大小 35M)。然后解压某个目录下,比如
unzip instantclient-basiclite-macos.x64-19.8.0.0.0dbru.zip -d ~/Developers现在只要连接 Oracle 数据库前加上一行代码
1cx_Oracle.init_oracle_client(lib_dir="/Users/yanbin/Developers/instantclient_19_8")因此完整的操作 Oracle 数据库的代码是
1import cx_Oracle
2
3cx_Oracle.init_oracle_client(lib_dir="/Users/yanbin/Developers/instantclient_19_8")
4
5conn = cx_Oracle.connect('system', 'oracle', '192.168.86.10:1521/EE.oracle.docker')
6cursor = conn.cursor()
7cursor.execute('select * from dual')
8for row in cursor.fetchall():
9 print(row)
10conn.close()输出
('X',)除了调用 cx_Oracle.init_oracle_client 来指定 Oracle Instant Client 的位置外,还能用环境变量 LD_LIBRARY_PATH 的方式
export LD_LIBRARY_PATH=/Users/yanbin/Developers/instantclient_19_8:$LD_LIBRARY_PATH这样的话,就不需要 xc_Oracle.init_oracle_client() 这个函数调用了。
如何通过 SID 进行连接
前面提到 cx_Oracle 是用 service name 进行连接的,如查把 service name 换作 SID 是不能连接成功的,比如写成1conn = cx_Oracle.connect('system', 'oracle', '192.168.86.33:1521/ee')报错
cx_Oracle.DatabaseError: ORA-12514: TNS:listener does not currently know of service requested in connect descriptor这儿有两种方式
通过 SID 构造 DSN 字符串
1dsn_str = cx_Oracle.makedsn("192.168.86.33", "1521", "ee")
2conn = cx_Oracle.connect('system', 'oracle', dsn_str)此时的 dsn_str 是
(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.86.33)(PORT=1521))(CONNECT_DATA=(SID=ee)))或者在 tnsnames.ora 中找到 service name, 也可用 SID 连接数据库后,通过 SQL 语句
select * from global_name 找到 service name。JayDeBeApi 桥接 JDBC 到 Python
最后,有人尝试用 JayDeBeApi 在 Python 中使用 JDBC 数据库驱动来连接数据库,这样做似乎对 Oracle 来讲可以省去安装 Oracle Instant Client 软件包,但带来的负担更重了,还需要安装 Java 虚拟机,并且运行的时候还得启动 JVM,用 Python 一个很重要的原因就是要摆脱 Java。比如操作 DB2
1import jaydebeapi
2conn = jaydebeapi.connect(
3 jclassname="oracle.jdbc.OracleDriver",
4 url="jdbc:oracle:thin:@192.168.86.33:1521:ee",
5 driver_args=("system", "oracle"),
6 jars='/Users/yanbin/.m2/repository/com/oracle/database/jdbc/ojdbc8/21.5.0.0/ojdbc8-21.5.0.0.jar')
7cursor = conn.cursor()
8# use cursor standard APIs因为实际上扮演连接操作数据库的角色是 Java 的 JDBC, 所以 url 是一个 JDBC 连接字符串,JayDeBeApi 用 url 结合 driver_args 中的参数去调用 Java 的 Driver.getConnection(url, *driver_args) 方法。JayDeBeApi 所实现的是在 Java 与 Python 之间进行类型的转换,以调用相应的 JDBC 函数。
JayDeBeApi 依赖于 JVM,它借助于 JPype1 试图从 JAVA_HOME 或 PATH 环境变量中找到 java 执行程序启动 JVM。如果没有 JAVA_HOME 或 PATH 中找不到 java,则最后因系统而异采用不同的查找定位 JAVA_HOME 的办法,见 _jvmfinder.py,所以这会造成启动 JVM 非常的慢。
找到的 JVM 的路径可以通过 JPype1 代码打印出来
pip install jpype1
1from jpype import getDefaultJVMPath
2print(getDefaultJVMPath())如果定位到 JAVA_HOME, 输出类似如下的路径
/Library/Java/JavaVirtualMachines/jdk1.8.0_281.jdk/Contents/Home/jre/lib/jli/libjli.dylib否则报告
jpype._jvmfinder.JVMNotFoundException: No JVM shared library file (libjvm.so) found. Try setting up the JAVA_HOME environment variable properly.基于 JayDeBeApi 的实现原理,它几乎可以应用于所有 JDBC 驱动支持的数据库。
参考:
永久链接 https://yanbin.blog/python-connect-db2-oracle-databases/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。