## 1. 为什么选择JPype1+JDBC这条路?
最近在做一个数据整合项目,需要让Python程序去连接一个比较小众的数据库——InterSystems Cache。说实话,一开始我也走了弯路,想着用Python生态里最常见的`pyodbc`配合ODBC驱动去连,毕竟这条路子听起来最“标准”。但实际一跑,问题就来了:中文数据读出来老是乱码,长一点的文本字段动不动就被截断,调试了半天,各种编码参数都试遍了,还是解决不了。后来查资料才知道,对于一些特定数据库(尤其是像Cache这种),其官方提供的ODBC驱动在某些版本或场景下,对非ASCII字符集的支持可能并不完善,这坑踩得我够呛。
就在我头疼的时候,突然想起了Java那庞大而成熟的数据库生态。几乎你能想到的任何数据库,官方或社区都会提供高质量、功能完整的JDBC驱动。那能不能让Python“借用”一下Java的JDBC驱动呢?这样一来,不就绕开了ODBC驱动的那些兼容性问题了吗?这个思路一打开,路子就宽了。
我最初尝试了`JayDeBeApi`这个库,它号称是Python里调用JDBC的桥梁。用了一下,基础功能是有的,但很快就遇到了新问题:当查询结果里包含`BLOB`(二进制大对象)这类特殊字段时,`JayDeBeApi`的类型转换会出问题,要么报错,要么返回一个无法直接处理的Java对象。好奇心驱使我去翻了翻它的源码,结果发现,它底层依赖的其实是另一个更基础的库——`JPype1`。`JayDeBeApi`相当于在`JPype1`之上封装了一层符合Python DB-API 2.0规范的接口。
既然底层都是`JPype1`,那我为什么不直接研究`JPype1`呢?说不定控制更精细,能解决的问题也更多。这一查,发现了个好消息:从`JPype1`的**1.1.1版本**开始,它原生内置了对Python DB-API 2.0规范的支持!这意味着,我们不再需要`JayDeBeApi`这个“中间商”,直接用`JPype1`就能以符合Python数据库编程习惯的方式(比如使用`connect`、`cursor`、`execute`)来操作JDBC了,代码会更简洁,对数据类型的掌控力也更强。
所以,如果你也在为Python连接某些数据库(特别是那些ODBC驱动不给力的小众或企业级数据库)而烦恼,或者需要利用某个只有JDBC驱动才提供的独家功能,那么`JPype1` + `JDBC`这条技术路线,绝对值得你花时间了解一下。它不是什么“屠龙之术”,而是一个能实实在在解决生产环境连接问题的实用方案。
## 2. 手把手搭建你的开发环境
工欲善其事,必先利其器。用Python调JDBC,听起来有点“跨界”,但其实环境搭建并不复杂,核心就三样东西:合适的Java环境、正确版本的`JPype1`,以及你要连接的那个数据库的JDBC驱动JAR包。
### 2.1 Java环境是基石
既然要调用JDBC,那Java运行环境(JRE)或者Java开发工具包(JDK)是必不可少的。我强烈推荐使用 **JDK 1.8(也就是Java 8)或更高版本**。Java 8是一个非常稳定且被广泛支持的长期支持版本,绝大多数JDBC驱动都能很好地兼容它。
安装好JDK后,**最关键的一步是设置`JAVA_HOME`环境变量**。这个变量告诉`JPype1`你的Java装在哪里。如果没设置,启动JVM时会报错。
- **在Windows上**:你可以在“系统属性” -> “高级” -> “环境变量”中,新建一个系统变量,变量名为`JAVA_HOME`,变量值是你的JDK安装路径,比如`C:\Program Files\Java\jdk1.8.0_301`。
- **在Linux/macOS上**:通常可以在你的shell配置文件(如`~/.bashrc`或`~/.zshrc`)里添加一行:`export JAVA_HOME=/usr/lib/jvm/java-8-openjdk`(具体路径请根据你的实际安装情况调整)。
设置完后,打开一个新的命令行终端,输入`echo $JAVA_HOME`(Linux/macOS)或在CMD里输入`echo %JAVA_HOME%`(Windows),如果能正确显示路径,就说明设置成功了。
### 2.2 安装正确版本的JPype1
这是整个方案的核心Python库。前面我们提到了,必须使用 **1.1.1及以上版本**,因为它才内置了`dbapi2`模块,这是我们能像使用`sqlite3`或`pymysql`一样操作数据库的关键。
安装非常简单,直接用pip命令就行。我建议使用`>=`来指定最低版本,让pip自动安装最新的兼容版本,这样能获得最好的稳定性和功能支持。
```bash
pip install JPype1>=1.1.1
```
安装完成后,可以在Python交互环境里验证一下:
```python
import jpype
print(jpype.__version__) # 应该输出 1.1.1 或更高的版本号
```
### 2.3 准备你的“武器库”:JDBC驱动JAR包
这是连接特定数据库的钥匙。你需要从数据库的官方网站下载对应的JDBC驱动。通常是一个以`.jar`结尾的文件。
- **MySQL**:去MySQL官网下载`mysql-connector-java-x.x.xx.jar`。
- **Oracle**:去Oracle官网下载`ojdbcx.jar`(版本号如`ojdbc8.jar`对应Java 8)。
- **InterSystems IRIS/Cache**:从InterSystems的安装目录或官网获取,例如`intersystems-jdbc-3.1.0.jar`或更早的`cache-jdbc-2.0.0.jar`。
- **其他数据库(如PostgreSQL, SQL Server)**:同理,寻找其官方的JDBC驱动JAR包。
**一个小建议**:把这个JAR包放在你的项目目录里,或者一个固定的、好记的路径下。因为在代码里,我们需要指定这个JAR文件的路径来启动Java虚拟机(JVM)。
## 3. 实战:从连接Cache数据库开始
理论说了不少,咱们直接上代码,看看怎么用`JPype1`连上那个让我一开始头疼的InterSystems Cache(及其新一代产品IRIS)数据库。这里会提供两个版本的示例,因为它的驱动有过变化,搞清楚区别能避免很多坑。
### 3.1 连接InterSystems IRIS (新驱动)
InterSystems把原来的Cache数据库升级整合成了IRIS数据平台。新版本对应的JDBC驱动包通常是`intersystems-jdbc-3.x.x.jar`。连接代码的结构非常清晰,我一步步拆开讲。
```python
#!/usr/bin/env python
# -*- coding:utf-8 -*-
try:
# 1. 导入关键模块
import jpype.imports # 这个导入允许我们使用‘from java.xx import xx’的语法
from jpype import JInt, JShort # 用于处理Java的整数和短整数类型
from jpype import dbapi2 # 核心!DB-API 2.0接口
# 2. 启动Java虚拟机(JVM),并指定JDBC驱动JAR包路径
# 注意:classpath参数接受一个列表。路径是相对于你启动Python脚本的目录,不是脚本文件本身的位置。
# 比如你在 /home/user 目录下执行 python my_script.py,那么 classpath 里的路径就从 /home/user 开始找。
jpype.startJVM(classpath=['./drivers/intersystems-jdbc-3.1.0.jar'])
# 3. 像导入Python模块一样导入Java类
# 这行代码之后,你就可以使用 com.intersystems.jdbc 包里的所有类了
from com.intersystems.jdbc import *
# 4. 版本检查,确保JPype1版本符合要求
if jpype.__version__ < '1.1.0':
raise EnvironmentError('jpype1 need version >=1.1.0, please check jpype1 version')
except ImportError as e:
# 如果导入失败,可能是JPype1没安装好
raise ImportError('Import jpype occurred an error: ', e)
# 5. 构造JDBC连接字符串
# 这是JDBC连接的核心,格式是 `jdbc:子协议://主机:端口/数据库名?参数`
# 对于IRIS,子协议是 `IRIS`。参数 `useUnicode=true&characterEncoding=UTF-8` 对解决中文乱码至关重要。
conn_url = "jdbc:IRIS://192.168.1.100:1972/MYDB?useUnicode=true&characterEncoding=UTF-8"
# 6. 准备连接参数
# driver_args 是一个字典,里面放用户名、密码等认证信息
driver_args = {"user": "TEST_USER", "password": "MyPass123"}
# 7. 建立数据库连接!
# 使用 dbapi2.connect 方法,指定驱动类全名和参数
# 驱动类名可以在驱动文档里找到,对于IRIS新驱动是 `com.intersystems.jdbc.IRISDriver`
conn = dbapi2.connect(conn_url,
driver='com.intersystems.jdbc.IRISDriver',
driver_args=driver_args)
# 8. (可选但重要)自定义类型转换器
# Java的 Integer (JInt) 和 Short (JShort) 对象返回给Python时,默认可能是Java对象。
# 这里我们将其转换器设置为Python的 `str` 函数,让它们自动转成Python字符串。
# 你也可以定义更复杂的函数来处理,比如把特定的Java类型转成Python的datetime。
conn.converters[JInt] = str
conn.converters[JShort] = str
# 至此,连接已建立!后面的操作就和用pymysql、sqlite3一模一样了。
# 9. 创建游标,执行查询
cur = conn.cursor()
# 使用参数化查询,防止SQL注入,也更清晰
sql_query = 'SELECT CODE, NAME FROM DEPARTMENT WHERE NAME = ?'
cur.execute(sql_query, ("技术研发部",))
# 10. 获取结果
# fetchone() 取一条记录,fetchall() 取所有
row = cur.fetchone()
print(f"查询结果(元组): {row}") # 输出类似:('D001', '技术研发部')
# 11. 将结果转为字典(更易用)
# cur.description 包含了字段的元信息,如字段名
if row and cur.description:
# 提取字段名并转为大写(通常更规范)
column_names = [col[0].upper() for col in cur.description]
# 用zip将字段名和值配对,创建字典
row_dict = dict(zip(column_names, row))
print(f"查询结果(字典): {row_dict}") # 输出:{'CODE': 'D001', 'NAME': '技术研发部'}
# 12. 别忘了关闭连接,释放资源
cur.close()
conn.close()
```
**代码要点解析**:
- **`jpype.imports`**:这个导入是魔法发生的关键,它启用了从Java包直接导入类的语法糖,让代码看起来更“Pythonic”。
- **`classpath`路径**:这是新手最容易出错的地方。`startJVM`中的`classpath`是相对于**你运行Python命令的当前工作目录**,而不是脚本文件所在的目录。我建议使用绝对路径,或者用`os.path`模块来构造相对路径,这样最稳妥。
- **连接字符串参数**:`useUnicode=true&characterEncoding=UTF-8` 这两个参数对于处理中文等非拉丁字符集几乎是必须的,能确保数据在传输和转换过程中编码正确。
- **类型转换器**:`conn.converters` 是一个字典,你可以为不同的Java类型注册Python转换函数。这是一个非常强大的功能,可以优雅地解决Java和Python类型系统之间的差异。
### 3.2 连接旧版Cache数据库
如果你的环境还在使用更早的Cache数据库和对应的`cache-jdbc-2.0.0.jar`驱动,代码只有细微差别,主要体现在驱动类名和连接字符串的子协议上。
```python
# ... 前面的导入和JVM启动代码类似,但classpath指向旧版jar包 ...
jpype.startJVM(classpath=['./drivers/cache-jdbc-2.0.0.jar'])
from com.intersys.jdbc import * # 注意包名是 intersys,不是 intersystems
# 连接字符串的子协议变成了 `Cache`
conn_url = "jdbc:Cache://192.168.1.100:1972/MYDB?useUnicode=true&characterEncoding=UTF-8"
driver_args = {"user": "TEST_USER", "password": "MyPass123"}
# 驱动类名也变了
conn = dbapi2.connect(conn_url,
driver='com.intersys.jdbc.CacheDriver', # 这里是 CacheDriver
driver_args=driver_args)
# ... 后续的查询、类型转换、关闭连接等操作完全一样 ...
```
**关键区别总结**:
- **JAR包和包名**:旧驱动是`com.intersys`,新驱动是`com.intersystems`。导入时写错会报`ClassNotFound`错误。
- **驱动类**:旧的是`CacheDriver`,新的是`IRISDriver`。
- **连接子协议**:旧的是`jdbc:Cache://`,新的是`jdbc:IRIS://`。
## 4. 举一反三:连接Oracle与MySQL
掌握了Cache/IRIS的连接方法,其他数据库就是“换汤不换药”了。核心步骤永远是:**找对JAR包、写对连接字符串、用对驱动类名**。下面我给出Oracle和MySQL的完整连接示例,你可以对比着看,理解其中的模式。
### 4.1 连接Oracle数据库
Oracle的JDBC驱动(ojdbc)版本比较多,需要根据你的Java版本和Oracle数据库版本来选择。这里以常用的`ojdbc8.jar`为例。
```python
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import jpype.imports
from jpype import dbapi2
import os
# 假设我们把 ojdbc8.jar 放在项目根目录的 libs 文件夹下
jar_path = os.path.join(os.path.dirname(__file__), 'libs', 'ojdbc8.jar')
jpype.startJVM(classpath=[jar_path])
# 导入Oracle JDBC相关的Java类(非必须,但有时IDE需要)
# from oracle.jdbc.driver import *
# Oracle JDBC连接字符串
# 格式:jdbc:oracle:thin:@//主机:端口/服务名
# 也可以是:jdbc:oracle:thin:@主机:端口:实例名 (老格式)
host = "10.10.10.5"
port = "1521"
service_name = "ORCLPDB" # 或者是SID
conn_url = f"jdbc:oracle:thin:@//{host}:{port}/{service_name}"
# 连接参数
driver_args = {
"user": "scott",
"password": "tiger"
}
# 建立连接
# Oracle的驱动类名是 `oracle.jdbc.OracleDriver`
conn = dbapi2.connect(conn_url,
driver='oracle.jdbc.OracleDriver',
driver_args=driver_args)
cur = conn.cursor()
# 执行一个简单的查询
cur.execute("SELECT EMPNO, ENAME, JOB FROM EMP WHERE DEPTNO = :1", (20,))
# 获取所有结果
rows = cur.fetchall()
for empno, ename, job in rows:
print(f"员工号: {empno}, 姓名: {ename}, 职位: {job}")
# 关闭连接
cur.close()
conn.close()
```
**Oracle连接注意事项**:
- **驱动版本**:`ojdbc8.jar` 适用于Java 8及更高版本,连接Oracle 11g、12c、19c等主流版本。如果Java版本是11+,也可以考虑`ojdbc10.jar`或`ojdbc11.jar`。
- **服务名 vs SID**:现代Oracle数据库多使用“服务名”(Service Name),连接字符串格式如示例所示。如果是老式的“系统标识符”(SID),格式可能是`jdbc:oracle:thin:@host:port:SID`。
- **`useUnicode`参数**:对于Oracle,如果遇到中文乱码,通常需要确保数据库服务器字符集、客户端NLS_LANG环境变量和Python代码的编码一致,JDBC连接字符串本身相关参数较少。
### 4.2 连接MySQL数据库
MySQL的JDBC驱动(Connector/J)是下载和使用最方便的之一。
```python
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import jpype.imports
from jpype import dbapi2
import os
# 加载MySQL驱动jar包
jar_path = os.path.join(os.path.dirname(__file__), 'drivers', 'mysql-connector-java-8.0.33.jar')
jpype.startJVM(classpath=[jar_path])
# MySQL JDBC连接字符串
# 格式:jdbc:mysql://主机:端口/数据库名?参数1=值1&参数2=值2
host = "localhost"
port = "3306"
database = "testdb"
conn_url = f"jdbc:mysql://{host}:{port}/{database}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"
# 连接参数
driver_args = {
"user": "root",
"password": "your_password"
}
# 建立连接
# MySQL的驱动类名是 `com.mysql.cj.jdbc.Driver` (8.0版本)
# 如果是老版本(5.x),可能是 `com.mysql.jdbc.Driver`
conn = dbapi2.connect(conn_url,
driver='com.mysql.cj.jdbc.Driver',
driver_args=driver_args)
cur = conn.cursor()
# 执行查询
cur.execute("SELECT id, username, email FROM users WHERE active = %s", (True,))
# 遍历结果
for user_id, username, email in cur.fetchall():
print(f"ID: {user_id}, 用户名: {username}, 邮箱: {email}")
# 插入数据的例子
insert_sql = "INSERT INTO logs (message, level) VALUES (%s, %s)"
cur.execute(insert_sql, ("系统启动完成", "INFO"))
conn.commit() # 记住要提交事务!
print(f"插入成功,影响行数: {cur.rowcount}")
cur.close()
conn.close()
```
**MySQL连接关键点**:
- **驱动类名**:MySQL Connector/J 8.0及以上版本使用`com.mysql.cj.jdbc.Driver`。5.x版本使用`com.mysql.jdbc.Driver`。用错了会报错。
- **连接参数**:
- `useUnicode=true&characterEncoding=utf8`:确保正确处理中文。
- `serverTimezone=Asia/Shanghai`:**非常重要!** 如果不设置,在处理数据库的`DATETIME`或`TIMESTAMP`字段时,可能会遇到时区转换错误或时间不对的问题。请根据你的服务器所在地设置正确的时区。
- **参数化查询**:MySQL使用`%s`作为占位符,而不是`?`。这是Python DB-API规范中一个常见的数据库差异点。
## 5. 进阶技巧与避坑指南
当你成功连接并执行了基本查询后,可能会遇到一些更复杂的情况。下面这些技巧是我在实际项目中踩过坑后总结出来的,能帮你走得更稳。
### 5.1 处理复杂数据类型与自定义转换
JDBC驱动返回的数据类型是Java类型。`JPype1`的`dbapi2`模块会尝试进行一些基础转换,但遇到`BLOB`、`CLOB`、`TIMESTAMP WITH TIME ZONE`或者自定义的Java对象时,可能就需要我们手动干预了。
假设我们有一个表,其中包含一个`BLOB`类型的头像图片字段。
```python
from jpype import JByte, JArray, JClass
import io
# ... 连接数据库的代码 ...
# 假设我们想读取用户ID为1的头像(BLOB字段)
cur.execute("SELECT avatar FROM user_profile WHERE user_id = ?", (1,))
row = cur.fetchone()
if row:
avatar_blob = row[0] # 此时avatar_blob是一个Java的byte[]数组对象
# 方法1:使用JPype工具转换为Python bytes
if avatar_blob:
# 将Java的byte[]转换为Python的bytes
python_bytes = bytes(avatar_blob) # 或者使用 jpype.JArray(JByte)(avatar_blob) 进行转换
# 现在你可以用PIL(Pillow)等库打开这个图片了
# from PIL import Image
# image = Image.open(io.BytesIO(python_bytes))
print(f"成功读取BLOB,长度:{len(python_bytes)} 字节")
# 更通用的方法是注册一个自定义转换器
def handle_blob(java_blob_obj):
"""将Java byte[]对象转换为Python bytes"""
if java_blob_obj is None:
return None
# 获取java.sql.Blob对象的内容
# 注意:这里假设驱动返回的是byte[],有些驱动可能直接返回java.sql.Blob对象
# 如果是java.sql.Blob,需要调用其 getBytes(1, int(blob.length())) 方法
if hasattr(java_blob_obj, '__java_name__') and 'byte[' in java_blob_obj.__java_name__:
return bytes(java_blob_obj)
else:
# 处理其他可能的Blob类型
return None
# 假设我们通过某种方式知道了某个列返回的Java类型是 byte[]
ByteArrayClass = JClass("byte[]")
conn.converters[ByteArrayClass] = handle_blob
```
**自定义转换器的核心思路**:`conn.converters`是一个映射字典,键是Java的类(或`jpype`类型,如`JInt`),值是一个Python可调用对象(函数)。当从结果集中读取数据时,如果遇到匹配的Java类型,就会调用你注册的函数进行转换,返回值将作为Python端的结果。
### 5.2 管理JVM生命周期与资源
`jpype.startJVM()`启动的是一个真正的Java虚拟机。它比较“重”,通常一个Python进程只需要启动一次。
- **不要重复启动JVM**:在同一个Python进程中多次调用`startJVM()`会报错。最佳实践是在程序入口(如主函数开头)启动一次,然后在整个程序生命周期内使用它。
- **关闭JVM**:虽然Python程序退出时会自动清理,但显式关闭是个好习惯,尤其是在长时间运行的后台脚本或Web应用中。使用`jpype.shutdownJVM()`来关闭。
- **内存与性能**:JVM本身会占用一定内存。如果你的Python程序只是偶尔操作数据库,频繁启动/关闭JVM开销会很大。可以考虑将数据库操作封装成一个常驻服务,或者使用连接池来复用连接和JVM环境。
### 5.3 连接池与多线程考量
在生产环境中,为每个请求都新建一个数据库连接是非常低效的。虽然`JPype1` + `JDBC`本身不直接提供连接池,但我们可以利用Java生态中成熟的连接池库,比如**HikariCP**或**Apache DBCP**。
思路是:在JVM中初始化一个Java侧的连接池对象,然后Python代码每次从这个池子里“借”连接来用。
```python
import jpype.imports
from jpype import JClass, JString
# 启动JVM,需要加载连接池和数据库驱动两个JAR
jpype.startJVM(classpath=[
'./libs/hikaricp-4.0.3.jar',
'./libs/mysql-connector-java-8.0.33.jar'
])
# 导入Java类
HikariConfig = JClass('com.zaxxer.hikari.HikariConfig')
HikariDataSource = JClass('com.zaxxer.hikari.HikariDataSource')
# 配置连接池
config = HikariConfig()
config.setJdbcUrl("jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8")
config.setUsername("root")
config.setPassword("your_password")
config.setMaximumPoolSize(10) # 最大连接数
config.setMinimumIdle(5) # 最小空闲连接
# 创建数据源
data_source = HikariDataSource(config)
# 现在,我们可以从数据源获取连接,但注意获取到的是Java的Connection对象
# 需要将其“包装”成dbapi2的连接对象(如果dbapi2支持的话,或者直接使用Java方式操作)
# 一个更直接的方式是:在Python端维护一个简单的连接队列(非生产级),或者直接使用Java Connection对象。
# 对于复杂场景,可能需要更深入的jpype交互。
# 示例:直接使用Java Connection(非dbapi2风格)
try:
java_conn = data_source.getConnection()
java_stmt = java_conn.createStatement()
java_rs = java_stmt.executeQuery("SELECT 1")
if java_rs.next():
print(f"查询结果: {java_rs.getInt(1)}")
java_rs.close()
java_stmt.close()
finally:
if java_conn:
java_conn.close() # 这里close()实际是将连接归还给池
# 程序结束时关闭数据源(从而关闭整个连接池)
data_source.close()
```
这种方式更进阶,需要对Java和`JPype1`的交互有更深的理解。对于大多数Python项目,如果只是需要连接池,使用纯Python的数据库库(如`aiomysql`、`asyncpg`配合异步框架,或`SQLAlchemy`的池化功能)连接主流数据库可能是更简单的选择。但对于必须通过JDBC连接的数据库,上述Java侧连接池方案是可行的生产级路径。
### 5.4 常见错误与排查方法
1. **`ClassNotFoundException` 或 `NoClassDefFoundError`**:
- **原因**:JVM在`classpath`中找不到指定的类。
- **排查**:
- 检查`startJVM(classpath=[...])`中的JAR包路径是否正确、绝对。
- 检查驱动类名是否拼写正确,比如`com.mysql.cj.jdbc.Driver` vs `com.mysql.jdbc.Driver`。
- 确认JAR包版本与数据库版本兼容。
2. **`SQLException: No suitable driver found`**:
- **原因**:连接字符串的URL格式不对,或者驱动类没有正确加载(虽然JAR包在classpath里,但有些旧驱动需要显式调用`Class.forName`)。
- **排查**:
- 检查连接字符串的`jdbc:子协议://`部分是否正确。每个数据库的“子协议”不同(`mysql`、`oracle:thin`、`IRIS`等)。
- 对于极少数老驱动,可能需要在Python中手动加载一下驱动类:`JClass('com.xxx.xxxDriver')`,然后再调用`dbapi2.connect`。
3. **中文乱码问题**:
- **原因**:数据在传输、转换过程中编码不一致。
- **解决**:
- **确保连接字符串包含编码参数**:如MySQL的`characterEncoding=utf8`,Oracle的`useUnicode=true`等。
- **检查数据库、表、字段的字符集**:确保它们都是`UTF-8`或`GBK`等与你程序匹配的编码。
- **统一Python文件编码**:在脚本开头使用`# -*- coding:utf-8 -*-`。
4. **性能问题**:
- **现象**:查询速度慢,尤其是批量操作。
- **优化**:
- 使用**参数化查询**(`cursor.execute(sql, params)`),不要拼接SQL字符串,这既能防注入也能利用数据库的查询缓存。
- 对于批量插入,考虑使用`cursor.executemany()`(如果驱动支持)。
- 查询时只选取需要的列,避免`SELECT *`。
- 合理使用事务。
这条路子虽然看起来比直接使用`pymysql`或`cx_Oracle`绕了一点,但它为你打开了一扇门,让你能用Python几乎无限制地访问任何提供了JDBC驱动的数据源。当你下一次遇到一个冷门数据库,或者需要用到某个只有JDBC驱动才支持的高级功能时,希望这篇文章和这些代码能成为你工具箱里一件趁手的武器。