Jython数据库编程全解析
在数据库编程领域,Jython提供了丰富的功能和多样的工具,能帮助开发者高效地完成各类数据库操作。本文将详细介绍Jython在数据库编程中的多种应用,包括DBM文件、序列化、数据库管理系统、JDBC以及zxJDBC等方面的内容。
1. DBM文件
DBM文件是哈希数据库文件,其行为类似于字典对象,但存在一定限制。当前实现并未使用所有字典方法,且所有DBM键和值都必须是字符串。目前,Jython仅捆绑了dumbdbm这一DBM工具,虽然其名称缺乏吸引力,实现简单且速度较慢,但它是一个相当有效的DBM克隆工具。
以下是使用dumbdbm模块的简单交互式示例:
>>> import dumbdbm
>>> dbm = dumbdbm.open("dbmtest")
>>> dbm['Value 1'] = 'A'
>>> dbm['Value 2'] = 'B'
>>> dbm['Value 3'] = 'B'
>>> for key in dbm.keys():
... print key, ":", dbm[key]
...
Value 3 : B
Value 2 : B
Value 1 : A
>>>
>>> dir(dbm.__class__)
['__delitem__', '__doc__', '__getitem__', '__init__', '__len__',
'__module__', '__setitem__', '_addkey', '_addval', '_commit', '_setval',
'_update', 'close', 'has_key', 'keys']
>>>
>>> dbm.close()
dir(dbm. class )方法展示了dbm对象与字典的差异。在字典方法中,仅实现了keys和has_key方法。不过,DBM对象具有持久性这一显著优势,重新打开dbmtest目录可显示先前输入的值。
打开新的dumbdbm目录实际上会创建两个文件:一个扩展名为.dir,另一个扩展名为.dat。在open函数中,只需指定目录名称,无需扩展名。其他DBM实现允许在open函数中使用标志和模式参数,但dumbdbm会忽略这些参数。
2. 序列化
序列化是将对象转换为适合传输或存储的流的过程。由于序列化对象通常存储在数据库中,因此该主题与数据库密切相关。要序列化Jython对象,可使用marshal、pickle或cPickle模块。
- marshal模块 :该模块用于序列化代码对象和内置数据对象。不过,它最常被cPickle模块取代,但在某些项目中仍会出现。marshal模块有四个方法:dump、dumps、load和loads。dump和load方法用于将对象序列化到文件和从文件恢复对象,而dumps和loads方法则用于将对象序列化到字符串和从字符串恢复对象。
以下是使用marshal模块处理列表对象的交互式示例:
>>> import marshal
>>> file = open("myMarshalledData.dat", "w+b") # 必须为二进制模式
>>> L = range(30, 50, 7)
>>> marshal.dump(L, file) # 将对象L序列化到 'file'
>>>
>>> file.flush() # 在dump后刷新或关闭以确保完整性
>>> file.seek(0) # 将文件指针移回开头以便load()
>>>
>>> restoredL = marshal.load(file)
>>> print L
[30, 37, 44]
>>> print restoredL
[30, 37, 44]
- pickle和cPickle模块 :这两个模块是序列化Jython对象的首选方法。它们的区别仅在于实现和性能,使用方式相同。大多数对象(除Java对象、代码对象以及具有复杂__getattr__和__setattr__方法的Jython对象外)都可以使用pickle或cPickle进行序列化。
pickle模块定义了四个函数,如下表所示:
| 方法 | 描述 |
| — | — |
| Dump(object, file[, bin]) | 将内置数据对象序列化为先前打开的文件对象。如果对象无法序列化,将引发PicklingError异常。pickle可以使用文本或二进制格式。第三个参数为零或缺失表示文本格式,非零表示二进制格式。 |
| Load(file) | 从先前打开的文件对象中读取并反序列化数据。如果pickled数据是以二进制模式写入的,则提供给load的文件对象也必须以二进制模式打开。 |
| dumps(object[, bin]) | 将对象序列化为字符串对象,而不是文件。如果对象无法序列化,将引发PicklingError异常。第二个参数为零或缺失表示文本格式,非零表示二进制格式。 |
| loads(string) | 将字符串反序列化为数据对象。 |
以下是使用pickle存储product类实例的简单交互式示例:
>>> import cPickle
>>> class product:
... def __init__(self, productCode, data):
... self.__dict__.update(data)
... self.productCode = productCode
...
>>> data = {'name':'widget', 'price':'112.95', 'inventory':1000}
>>> widget = product('1123', data)
>>> f = open(widget.productCode, 'wb')
>>> cPickle.dump(widget, f, 1)
>>> f.close()
恢复该实例时,需要确保在反序列化的命名空间中存在该类。
对象可以通过定义特殊方法__getstate__和__setstate__来控制其序列化和反序列化过程。
你还可以使用pickler和unpickler对象。以下是创建这些对象的示例:
>>> import cPickle
>>> f = open("picklertest", "w+b")
>>> p = cPickle.Pickler(f)
>>> u = cPickle.Unpickler(f)
>>>
>>> L = range(10)
>>> p.dump(L) # 使用pickler对象进行序列化
>>> f.seek(0)
>>> print u.load()
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
pickle可以处理递归数据结构、包含自身引用的对象以及嵌套数据结构。
- shelve模块 :Jython的shelve模块将DBM目录的便利性与pickle的序列化功能相结合,创建了持久对象存储。shelve的行为类似于字典,但它允许任何可pickle的对象作为值,而键仍然必须是字符串。
以下是使用shelve模块的交互式示例:
>>> import shelve, dumbdbm
>>> dbm = dumbdbm.open("dbmdata")
>>> shelf = shelve.Shelf(dbm)
>>> shelf['a list'] = range(10)
>>> shelf['a dictionary'] = {1:1, 2:2, 3:3}
>>> print shelf.keys()
['a list', 'a dictionary']
>>> shelf.close()
你也可以直接使用shelve的open方法。
- PythonObjectInputStream :在Java中序列化Jython对象的过程与序列化Java对象基本相同,唯一的例外是org.python.util.PythonObjectInputStream。该类有助于在反序列化继承自Java类的Jython对象时解析类。
以下是从Java序列化Jython对象的伪代码示例:
// 导入所需类
import java.io.*;
import org.python.core.*;
// 标识资源
String file = "someFileName";
// 进行序列化,创建一个OutputStream(这里是FileOutputStream)
// 然后创建一个ObjectOutputStream
FileOutputStream oStream = new FileOutputStream(file);
ObjectOutputStream objWriter = new ObjectOutputStream(oStream);
// 写入一个简单的Jython对象
objWriter.writeObject(new PyString("some string"));
// 清理
objWriter.flush();
oStream.close();
反序列化对象时,可以使用ObjectInputStream或PythonObjectInputStream。
3. 数据库管理系统
Jython可以与任何具有Java驱动程序的数据库配合使用,但本文重点介绍MySQL和PostgreSQL这两个数据库管理系统(DBMS)。这两个数据库因其广泛的应用、免费可用性、稳定性和丰富的文档而被选中。
- MySQL :MySQL是一个SQL数据库服务器,可在大多数类UNIX平台和Windows上运行。要运行相关示例,需要下载并安装MySQL及其关联的JDBC驱动程序。MySQL可从http://www.mysql.com/ 的“downloads”部分获取,MySQL JDBC驱动程序也位于该网站的同一下载部分,最新驱动程序和版本信息可在http://mmmysql.sourceforge.net/ 找到。
安装MySQL后,需要添加用户和数据库。以下是启动服务器的命令:
- *nix系统:
/pathToMysql/bin/safe_mysqld &
- Windows系统:
\pathToMysql\bin\winmysqladmin.exe
创建测试数据库的命令如下:
C:\mysql\bin>mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3 to server version: 3.23.39
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> CREATE DATABASE test;
Query OK, 1 row affected (0.33 sec)
可以使用
SHOW DATABASES
语句确认数据库是否存在。
添加用户jyuser并授予其对测试数据库的所有权限的命令如下:
mysql> USE test;
mysql> GRANT ALL ON test TO jyuser@localhost IDENTIFIED BY "beans";
Query OK, 0 rows affected (0.05 sec)
- PostgreSQL :PostgreSQL是一个功能强大的对象关系数据库管理系统(ORDBMS),可在大多数类UNIX平台和Windows 2000/NT上运行。要运行相关示例,需要使用PostgreSQL服务器和PostgreSQL JDBC驱动程序,可从http://www.postgresql.org/ 获取。
在类UNIX系统上安装PostgreSQL,需下载适合系统的包格式并运行包管理器。在Windows 2000/NT系统上安装则需要额外的步骤。
安装服务器后,需要初始化数据库集群:
initdb -D /path/to/datadirectory
通常数据目录为
/usr/local/pgsql/data
。
在
pg_hba.conf
文件中添加主机条目,以允许从特定机器连接到数据库。以下是一个本地连接的示例:
host test 127.0.0.1 255.255.255.255 crypt
认证类型包括Trust、Password、Crypt、Ident、Krb4、Krb5和Reject。
创建用户jyuser的命令如下:
[shell prompt]$ createuser -U postgres -d -P jyuser
Enter password for user "jyuser":
Enter it again:
Shall the new user be allowed to create more new users? (y/n) n
CREATE USER
创建测试数据库的命令如下:
[shell prompt]$ createdb -U jyuser test
CREATE DATABASE
启动服务器的命令如下:
postmaster -i -D /usr/local/pgsql/data &
需要使用
-i
选项以允许网络套接字连接。
4. JDBC
Java使用JDBC和java.sql包与SQL数据库进行交互,Jython也可以使用这些工具。使用JDBC API和java.sql包与SQL数据库交互,只需要为所使用的数据库提供适当的JDBC驱动程序。
-
连接到数据库
-
JDBC URL :URL是唯一标识数据库连接的字符串,语法为
jdbc:<subprotocol>:<subname>。不同数据库的URL语法有所不同,例如MySQL和PostgreSQL的URL语法如下:-
MySQL:
jdbc:mysql://[hostname][:port]/databaseName[parameter=value] -
PostgreSQL:
jdbc:postgresql://[hostname][:port]/databaseName[parameter=value]
连接本地测试数据库的URL可以简化为: -
MySQL:
jdbc:mysql:///test -
PostgreSQL:
jdbc:postgresql:///test
还可以在URL末尾指定连接参数,如下表所示:
| 参数 | 含义 | 默认值 | 适用数据库 |
| — | — | — | — |
| User | 数据库用户名 | 无 | 两者 |
| password | 密码 | 无 | 两者 |
| autoReconnect | 连接断开时是否尝试重新连接?(true|false) | false | MySQL |
| maxReconnects | 驱动程序应尝试重新连接的次数(假设autoReconnect为true) | 3 | MySQL |
| initialTimeout | 重新连接前等待的秒数(假设autoReconnect为true) | 2 | MySQL |
| maxRows | 返回的最大行数(0表示所有行) | 0 | MySQL |
| useUnicode | 是否使用Unicode?(true|false) | false | MySQL |
| characterEncoding | 使用的Unicode字符编码(如果useUnicode为true) | 无 | MySQL |
-
MySQL:
-
JDBC连接 :建立数据库连接的步骤如下:
- 将适当的驱动程序包含在类路径中。
- 向JDBC DriverManager注册驱动程序。
- 向java.sql.DriverManager.getConnection方法提供JDBC URL、用户名和密码。
-
以下是添加MySQL和PostgreSQL JDBC驱动程序到类路径的命令:
- MySQL:
set CLASSPATH=\path\to\mm_mysql-2_0_4-bin.jar;%CLASSPATH%
- PostgreSQL:
set CLASSPATH=\path\to\jdbc7.1-1.2.jar;%CLASSPATH%
注册驱动程序的方法有两种:使用
-D
开关设置
jdbc.drivers
属性,或使用
java.lang.Class.forName(classname)
方法。
以下是使用
Class.forName
加载MySQL和PostgreSQL驱动程序的示例:
import java
try:
java.lang.Class.forName("org.gjt.mm.mysql.Driver")
java.lang.Class.forName("org.postgresql.Driver")
except java.lang.ClassNotFoundException:
print "No appropriate database driver found"
raise SystemExit
使用
DriverManager.getConnection
方法连接数据库,该方法有三种签名:
public static Connection getConnection(String url) throws SQLException
public static Connection getConnection(String url, Properties info)
throws SQLException
public static Connection getConnection(
String url, String user, String password) throws SQLException
以下是连接本地测试数据库的示例:
from java.sql import DriverManager
from java.util import Properties
# 使用getConnection(URL)方法
mysqlConn = DriverManager.getConnection(
"jdbc:mysql://localhost/test?user=jyuser&password=beans")
postgresqlConn = java.sql.DriverManager.getConnection(
"jdbc:postgresql://localhost/test?user=jyuser&password=beans")
# 使用getConnection(URL, Properties)方法
params = Properties()
params.setProperty("user", "jyuser")
params.setProperty("password", "beans")
mysqlConn = DriverManager.getConnection("jdbc:mysql://localhost/test",
params)
postgresqlConn =
DriverManager.getConnection("jdbc:postgresql://localhost/test",
params)
# 使用getConnection(URL, String, String)方法
mysqlConn =
DriverManager.getConnection("jdbc:mysql://localhost/test",
"jyuser", "beans")
postgresqlConn =
DriverManager.getConnection("jdbc:postgresql://localhost/test",
"jyuser", "beans")
-
连接脚本
:Jython的交互式解释器是理想的数据库客户端工具。为了避免每次使用数据库时都手动输入连接语句,可以使用Jython的
-i命令行选项,或者将交互式控制台包装在所需的连接和关闭语句中。
以下是一个数据库客户端启动脚本的示例:
# File: mystart.py
from java.lang import Class
from java.sql import DriverManager
from org.python.util import InteractiveConsole
import sys
url = "jdbc:mysql://localhost/%s"
user, password = "jyuser", "beans"
# 注册驱动程序
Class.forName("org.gjt.mm.mysql.Driver")
# 允许通过命令行参数可选地设置数据库
if len(sys.argv) == 2:
url = url % sys.argv[1]
else:
url = url % "test"
# 获取连接
dbconn = DriverManager.getConnection(url, user, password)
# 创建语句
try:
stmt = dbconn.createStatement()
except:
dbconn.close()
raise SystemExit
# 创建内部控制台
interp = InteractiveConsole({"dbconn":dbconn, "stmt":stmt}, "DB Shell")
try:
interp.interact("Jython DB client")
finally:
# 关闭资源
stmt.close()
dbconn.close()
print
要连接到测试数据库,只需确保适当的驱动程序在类路径中,然后运行
jython mystart.py
。
- 连接对话框 :客户端应用程序通常使用对话框窗口获取数据库连接信息。以下是一个数据库连接对话框的示例:
# File: DBLoginDialog.py
import sys
import java
from java import awt
from java import sql
import pawt
class DBLoginDialog(awt.Dialog):
'''DBLoginDialog提示用户输入数据库信息并建立数据库登录。注册一个连接接收器作为客户端,例如:
def connectionClient(dbconnection):
# 对数据库连接进行操作
dbl = DBLoginDialog(parentframe, message)
dbl.client = connectionClient'''
def __init__(self, parentFrame, message):
awt.Dialog.__init__(self, parentFrame)
self.client = None
self.background = pawt.colors.lightgrey
bag = pawt.GridBag(self, anchor='WEST', fill='NONE')
# 创建所需组件
self.Hostname = HighlightTextField("localhost", 24)
self.DBMS = awt.Choice(itemStateChanged=self.updatePort)
self.Port = HighlightTextField("3306", 5)
self.Database = HighlightTextField("test",12)
self.User = HighlightTextField("jyuser", 10)
self.Password = HighlightTextField("", 10, echoChar='*')
# 填充选择组件选项
self.DBMS.add("MySQL")
self.DBMS.add("PostgreSQL")
# 添加消息
bag.addRow(awt.Label(message), anchor='CENTER')
# 将组件放入网格袋
for x in ["Hostname", "DBMS", "Port", "Database",
"User", "Password"]:
bag.add(awt.Label(x + ":"))
bag.addRow(self.__dict__[x])
# 添加动作按钮
bag.add(awt.Button("Login", actionPerformed=self.login),
fill='HORIZONTAL')
bag.addRow(awt.Button("Cancel", actionPerformed= self.close),
anchor='CENTER')
self.pack()
bounds = parentFrame.bounds
self.bounds = (bounds.x + bounds.width/2 - self.width/2,
bounds.y + bounds.height/2 - self.height/2,
self.width, self.height)
def login(self, e):
db = self.DBMS.selectedItem
if db == "MySQL":
driver = "org.gjt.mm.mysql.Driver"
else:
driver = "org.postgresql.Driver"
try:
java.lang.Class.forName(driver)
except java.lang.ClassNotFoundException:
self.showError("Unable to load driver %s" % driver)
return
url = "jdbc:%s://%s:%s/%s" % (db.lower(), self.Hostname.text,
self.Port.text,
self.Database.text)
try:
dbcon = sql.DriverManager.getConnection(url,
self.User.text,
self.Password.text)
self.dispose()
self.client(dbcon)
except sql.SQLException:
self.showError("Unable to connect to database")
def updatePort(self, e):
if self.DBMS.selectedItem == 'MySQL':
port = '3306'
elif self.DBMS.selectedItem == 'PostgreSQL':
port = '5432'
self.Port.text= port
def setClient(client):
self.client = client
def showError(self, message):
d = awt.Dialog(self.parent, "Error")
panel = awt.Panel()
panel.add(awt.Label(message))
button = awt.Button("OK")
panel.add(button)
d.add(panel)
d.pack()
bounds = self.parent.bounds
d.bounds = (bounds.x + bounds.width/2 -d.width/2,
bounds.y + bounds.height/2 - d.height/2,
d.width, d.height)
d.windowClosing = lambda e, d=d: d.dispose()
button.actionPerformed = d.windowClosing
d.visible = 1
def close(self, e):
self.dispose()
class HighlightTextField(awt.TextField, awt.event.FocusListener):
def __init__(self, text, chars, **kw):
awt.TextField.__init__(self, text, chars)
self.addFocusListener(self)
for k in kw.keys():
exec("self." + k + "=kw[k]")
def focusGained(self, e):
e.source.selectAll()
def focusLost(self, e):
e.source.select(0, 0)
def dummyClient(connection):
if connection != None:
print "\nDatabase connection successfully received by client."
print "Connection=", connection
connection.close()
print "Database connection properly closed by client."
if __name__ == '__main__':
# 创建一个虚拟框架作为对话框窗口的父窗口
f = awt.Frame("DB Login", windowClosing=lambda e: sys.exit())
screensize = f.toolkit.screenSize
f.bounds = (screensize.width/2 - f.width/2,
screensize.height/2 - f.height/2,
f.width, f.height)
# 创建并显示对话框窗口
dbi = DBLoginDialog(f, "Connection Information")
dbi.client = dummyClient
dbi.windowClosing = dbi.windowClosed = lambda e: sys.exit()
dbi.visible = 1
要执行该对话框,需确保适当的数据库驱动程序在类路径中,然后运行
jython DBLoginDialog.py
。
-
DatabaseMetaData
:连接到数据库后,可以使用
getMetaData方法获取数据库和连接的元数据。以下是一个交互式示例,用于探索MySQL的系统函数、数值函数和事务支持:
>>> import java
>>> from java.sql import DriverManager
>>>
>>> # 注册驱动程序
>>> java.lang.Class.forName("org.gjt.mm.mysql.Driver")
<jclass org.gjt.mm.mysql.Driver at 650329>
>>>
>>> # 获取连接
>>> url, user, password = "jdbc:mysql:///test", "jyuser", "beans"
>>> dbconn = DriverManager.getConnection(url, user, password)
>>>
>>> # 获取DatabaseMetaData对象
>>> md = dbconn.getMetaData()
>>>
>>> # 使用元数据对象查找MySQL的信息
>>> print md.systemFunctions
DATABASE,USER,SYSTEM_USER,SESSION_USER,PASSWORD,ENCRYPT,LAST_INSERT_ID,
VERSION
>>> md.numericFunctions
'ABS,ACOS,ASIN,ATAN,ATAN2,BIT_COUNT,CEILING,COS,COT,DEGREES,EXP,FLOOR,LOG,
LOG10,MAX,MIN,MOD,PI,POW,POWER,RADIANS,RAND,ROUND,SIN,SQRT,TAN,TRUNCATE'
>>> md.supportsTransactions()
0
>>>
>>> # 别忘了关闭连接
>>> dbconn.close()
还可以使用脚本比较MySQL和PostgreSQL的元数据。
-
语句
:与MySQL和PostgreSQL交互的主要方式是执行SQL语句。可以使用
createStatement方法创建语句对象,并使用execute、executeQuery和executeUpdate方法执行语句。
以下是创建表和插入数据的示例:
>>> import java
>>> java.lang.Class.forName("org.gjt.mm.mysql.Driver")
<jclass org.gjt.mm.mysql.Driver at 1876475>
>>> db, user, password = "test", "jyuser", "beans"
>>> url = "jdbc:mysql://localhost/%s" % db
>>> dbconn = java.sql.DriverManager.getConnection(url, user, password)
>>> stmt = dbconn.createStatement()
>>> query = "CREATE TABLE random ( number tinyint, letter char(1) )"
>>> stmt.execute(query)
0
>>> print stmt.resultSet
None
>>> import string, random
>>> for i in range(20):
... number = random.randint(0, 51)
... query = "INSERT INTO random (number, letter) VALUES (%i, '%s')"
... query = query % (number, string.letters[number - 1])
... stmt.executeUpdate(query)
1
...
使用
executeQuery
方法选择数据:
>>> rs = stmt.executeQuery("SELECT * FROM random")
>>> while rs.next():
... print rs.getString('letter'), ": ", rs.getInt('number')
...
...
U : 46
K : 36
h : 7
Q : 42
l : 11
u : 20
n : 13
z : 25
U : 46
Y : 50
j : 9
o : 14
i : 8
s : 18
d : 3
A : 26
K : 36
j : 9
n : 13
g : 6
>>> stmt.close()
>>> dbconn.close()
ResultSet对象提供了导航和更新记录的方法,但不同的JDBC版本和语句类型可能会有所不同。
如果需要更新结果集,需要满足一定条件,例如查询只包含一个表、不使用连接且选择表的主键,并且语句的并发类型设置为可更新。
以下是更新表并插入和更新记录的示例:
>>> import java
>>> from java import sql
>>> java.lang.Class.forName("org.gjt.mm.mysql.Driver")
<jclass org.gjt.mm.mysql.Driver at 1876475>
>>> user, password = "jyuser", "beans"
>>> url = "jdbc:mysql://localhost/test"
>>> dbconn = sql.DriverManager.getConnection(url, user, password)
>>>
>>> type = sql.ResultSet.TYPE_SCROLL_SENSITIVE
>>> concurrency = sql.ResultSet.CONCUR_UPDATABLE
>>> stmt = dbconn.createStatement(type, concurrency)
>>>
>>> # 更新表以包含主键
>>> query = "ALTER TABLE random ADD pkey INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY"
>>> stmt.execute(query)
0
>>>
>>> # 获取可更新的结果集
>>> rs = stmt.executeQuery("select * from random")
>>> rs.concurrency # 1008表示可更新
1008
>>>
>>> # 插入一行
>>> rs.moveToInsertRow()
>>> rs.updateInt('number', 7)
>>> rs.updateString('letter', 'g')
>>> rs.updateInt('pkey', 22)
>>> rs.insertRow() # 将其插入数据库
>>> rs.moveToCurrentRow()
>>>
>>> rs.relative(5) # 滚动5行
1
>>> # 打印当前行数据
>>> print rs.getString('letter'), rs.getInt('number')
h 8
>>> rs.updateInt('number', 3)
>>> rs.updateString('letter', 'c')
>>> rs.updateRow() # 将更新保存到数据库
>>> stmt.close()
>>> dbconn.close()
-
预编译语句
:预编译频繁使用的SQL语句可以提高效率。可以使用
prepareStatement方法创建预编译语句,并使用set*方法填充占位符。
以下是一个简单的预编译语句示例:
>>> import java
>>> from java import sql
>>> java.lang.Class.forName("org.postgresql.Driver")
<jclass org.gjt.mm.mysql.Driver at 1876475>
>>> dbconn = sql.DriverManager.getConnection(url, user, password)
>>> query = "INSERT INTO random (letter, number) VALUES (?, ?)"
>>> preppedStmt = dbconn.prepareStatement(query)
>>> preppedStmt.clearParameters()
>>> preppedStmt.setString(1, "f")
>>> preppedStmt.setInt(2, 6)
>>> preppedStmt.executeUpdate()
1
>>> preppedStmt.close()
>>> dbconn.close()
-
事务
:事务是一组必须全部成功完成或全部撤销的数据库操作。在PostgreSQL中使用JDBC事务,需要将连接的
autoCommit属性设置为0,并在出现错误时调用rollback方法。
以下是一个简单的事务示例:
>>> import java
>>> from java import sql
>>> java.lang.Class.forName("org.postgresql.Driver")
<jclass org.postgresql.Driver at 5711596>
>>> url = "jdbc:postgresql://localhost/test"
>>> con = sql.DriverManager.getConnection(url, "jyuser", "beans")
>>> stmt = con.createStatement()
>>> con.autoCommit = 0
>>> try:
... # 插入一个易于查找的字符
... query = "INSERT INTO random (letter, number) VALUES ('.', 0)"
... stmt.executeUpdate(query)
... print "First update successful."
... stmt.execute("Create an exception here")
... con.commit()
... except:
... print "Error encountered, rolling back."
... con.rollback()
...
1
First update successful.
Error encountered, rolling back.
>>>
>>> # 确认第一个更新语句已被撤销
>>> rs = stmt.executeQuery("SELECT * FROM random WHERE letter='.'")
>>> rs.next()
0
通过以上内容,我们详细介绍了Jython在数据库编程中的多种应用,包括DBM文件的使用、对象的序列化、与不同数据库管理系统的交互以及JDBC的各种操作。这些知识将帮助开发者更好地利用Jython进行数据库编程,提高开发效率和质量。
Jython数据库编程全解析
5. zxJDBC
虽然使用JDBC从Jython进行数据库操作很有价值,但它是一个Java API,其众多特定于Java类型的方法与Jython的高级、多态动态类型不太相符。而Python有一个数据库API,即Python DB API 2.0,但CPython使用的数据库驱动程序因底层C实现,对Jython往往无用。为填补这一空白,Brian Zimmer编写了zxJDBC,它不仅实现了DB API,还对其进行了扩展。
5.1 连接到数据库
使用zxJDBC包时,在调用连接函数之前,需要确保zxJDBC.jar和所需的JDBC驱动程序在类路径中。建立数据库连接的步骤如下:
1. 将适当的驱动程序和zxJDBC.jar文件包含在类路径中。
2. 向zxJDBC.connect()方法提供JDBC URL、用户名、密码和数据库驱动类的名称。
以下是设置类路径和连接数据库的示例:
# For MySQL
set CLASSPATH=mm_mysql-2_0_4-bin.jar;\path\to\zxJDBC.jar;%CLASSPATH%
# For PostgreSQL
set CLASSPATH=\path\to\jdbc7.1-1.2.jar;\path\to\zxJDBC.jar;%CLASSPATH%
from com.ziclix.python.sql import zxJDBC
mysqlConn = zxJDBC.connect("jdbc:mysql://localhost/test",
"jyuser", "beans",
"org.gjt.mm.mysql.Driver")
postgresqlConn = zxJDBC.connect("jdbc:postgresql://localhost/test",
"jyuser", "beans",
"org.postgresql.Driver")
如果需要设置特殊参数,可以作为关键字参数传递给connect函数:
url = "jdbc:mysql://localhost/test"
user = "jyuser"
password = "beans"
driver = "org.gjt.mm.mysql.Driver"
mysqlConn = zxJDBC.connect(url, user, password, driver,
autoReconnect="true")
连接错误会引发DatabaseError异常,可使用以下方式处理:
url = "jdbc:mysql://localhost/test"
user = "jyuser"
password = "beans"
driver = "org.gjt.mm.mysql.Driver"
try:
mysqlConn = zxJDBC.connect(url, user, password, driver,
autoReconnect="true")
except zxJDBC.DatabaseError:
pass
#handle error here
如果使用javax.sql包中的连接工厂或实现了javax.sql.DataSource或javax.sql.ConnectionPoolDataSource的类,可以使用zxJDBC.connectx方法:
from com.ziclix.python.sql import zxJDBC
userInfo = {'user':'jyuser', 'password':'beans'}
con = zxJDBC.connectx("org.gjt.mm.mysql.MysqlDataSource",
serverName='localhost', databaseName='test',
port=3306, **userInfo)
也可以通过JNDI查找获取连接:
from com.ziclix.python.sql import zxJDBC
con = zxJDBC.lookup("jndiName", key1=value1, key2=value2)
5.2 游标
zxJDBC游标是实际与数据库中的数据进行交互的对象,它是JDBC Statement和ResultSet对象的包装器。游标分为静态和动态两种类型,区别在于对结果集的处理方式:
- 动态游标是惰性的,仅在需要时遍历结果集,节省内存并均匀分配处理时间。
- 静态游标不是惰性的,会立即遍历整个结果集,会产生内存开销,但能在执行语句后很快知道行数。
以下是获取游标并执行查询的示例:
from com.ziclix.python.sql import zxJDBC
url = "jdbc:mysql://localhost/test"
user = "jyuser"
password = "beans"
driver = "org.gjt.mm.mysql.Driver"
con = zxJDBC.connect(url, user, password, driver,
autoReconnect="true")
cursor = con.cursor() # 静态游标
# 或者创建动态游标
cursor = con.cursor(1)
cursor.execute("SELECT * FROM random")
使用游标获取结果的方法有fetchone、fetchmany和fetchall:
cursor.fetchone()
cursor.fetchmany()
cursor.fetchmany(4)
cursor.fetchall()
执行查询后,可以通过游标对象的description属性查看结果集中行的信息:
cursor.description
游标对象的方法和属性如下表所示:
| 方法/属性 | 描述 |
| — | — |
| description | 描述查询结果中每列的信息,是一个包含名称、类型、显示大小、内部大小、精度、比例和可空性的七元组。 |
| rowcount | 结果中的行数,仅在游标为静态游标或动态游标完全遍历结果集后有效。 |
| callproc(procedureName, [parameters]) | 调用存储过程,仅适用于实现了存储过程的数据库。 |
| close() | 关闭游标。 |
| execute(statement) | 执行语句。 |
| executemany(statement, parameterList) | 使用参数列表执行语句,可以在语句中使用问号表示值,并在参数列表中提供替换值。 |
| fetchone() | 获取查询结果的一行。 |
| fetchmany([size]) | 如果没有提供参数,则返回arraysize行数;如果提供了参数,则返回指定数量的结果行。 |
| fetchall() | 获取所有剩余的结果行。 |
| nextset() | 继续处理下一个结果集,仅适用于支持多个结果集的数据库。 |
| arraysize | fetchmany()在没有参数时应返回的行数。 |
5.3 zxJDBC和元数据
Python DB API没有元数据规范,但zxJDBC通过一些连接和游标属性提供了一些连接元数据,这些属性与前面讨论的JDBC java.sql.DatabaseMetaData对象中的bean属性相匹配。如下表所示:
| zxJDBC属性 | DatabaseMetaData访问器 |
| — | — |
| connection.dbname | getDatabaseProductName |
| connection.dbversion | getDatabaseProductVersion |
| cursor.tables(catalog, schemapattern, tablepattern, types) | getTables |
| cursor.columns(catalog, schemapattern, tablenamepattern, columnnamepattern) | getColumns |
| cursor.foreignkeys(primarycatalog, primaryschema, pimarytable, foreigncatalog, foreignschema, foreigntable) | getCrossReference |
| cursor.primarykeys(catalog, schema, table) | getPrimaryKeys |
| cursor.procedures(catalog, schemapattern, procedurepattern) | getProcedures |
| cursor.procedurecolumns(catalog, schemapattern, procedurepattern, columnpattern) | getProcedureColumns |
| cursor.statistics(catalog, schema, table, unique, approximation) | getIndexInfo |
以下是从MySQL random数据库中提取一些元数据的示例:
from com.ziclix.python.sql import zxJDBC
url = "jdbc:mysql://localhost/test"
driver = "org.gjt.mm.mysql.Driver"
dbconn = zxJDBC.connect(url, "jyuser", "beans", driver)
print(dbconn.dbname)
print(dbconn.dbversion)
cursor = dbconn.cursor()
cursor.primarykeys(None, "%", "random")
print(cursor.fetchall())
cursor.tables(None, None, '%', None)
print(cursor.fetchall())
5.4 预编译语句
zxJDBC中,游标对象的executemany()方法类似于Java的预编译语句,允许在SQL语句中使用问号表示值,并在第二个参数中提供替换值:
sql = "INSERT INTO random (letter, number) VALUES (?, ?)"
cur.executemany(sql, ('Z', 51))
cur.execute("SELECT * from random where letter='Z'")
print(cur.fetchall())
5.5 错误和警告
zxJDBC中可能引发的异常如下:
-
Error
:通用异常。
-
DatabaseError
:针对特定数据库错误引发,连接对象和所有连接方法(connect、lookup、connectx)可能会引发此异常。
-
ProgrammingError
:针对编程错误(如缺少参数、错误的SQL语句)引发,游标对象和查找连接可能会引发此异常。
-
NotSupportedError
:当方法未实现时引发。
异常处理示例如下:
try:
pass # 假设这里有一个会引发错误的方法
except zxJDBC.DatabaseError:
pass # 处理DatabaseError
except zxJDBC.ProgrammingError:
pass # 处理ProgrammingError
except NotSupportedError:
pass # 处理不支持的错误
except zxJDBC.Error:
pass # 处理通用错误异常
可以通过游标对象的warnings属性获取警告信息,如果没有警告,则该属性为None。
5.6 dbexts
zxJDBC包中的dbexts是一个Python模块,它在Python DB API 2.0之上添加了另一层抽象。使用dbexts,可以在配置文件中指定连接信息,然后对定义的连接使用更高级的dbexts方法。
要使用dbexts,需要将zxJDBC附带的dbexts.py模块添加到sys.path中。配置文件默认名为dbexts.ini,位于dbexts.py文件所在的目录,也可以在dbexts构造函数中指定其他文件。以下是一个配置文件示例:
[default]
name=mysqltest
[jdbc]
name=mysqltest
url=jdbc:mysql://localhost/test
user=jyuser
pwd=beans
driver=org.gjt.mm.mysql.Driver
[jdbc]
name=pstest
url=jdbc:postgresql://localhost/test
user=jyuser
pwd=beans
driver=org.postgresql.Driver
连接数据库的示例如下:
from dbexts import dbexts
mysqlcon = dbexts("mysqltest", "dbexts.ini")
psgrscon = dbexts("pstest", "dbexts.ini")
psgrscon.raw("SELECT * from random")
psgrscon.isql("select * from random")
psgrscon.schema("random")
mysqlcon.table()
dbexts的主要方法如下表所示:
| 方法 | 描述 |
| — | — |
|
init
(dbname, cfg, resultformatter, autocommit) | dbexts构造函数,所有参数都有默认值,因此都是可选的。dbname是在dbexts.ini文件中为连接指定的名称;cfg是dbexts.ini文件的位置;resultformatter是一个可调用对象,用于显示数据;autocommit参数默认设置为1(true),也可以在调用构造函数时设置为0。 |
| isql(sql, params, bindings, maxrows) | 交互式执行SQL语句,执行后立即使用resultformatter显示结果。除了sql语句外,所有参数都有默认值。sql是SQL语句本身;params是用于替换SQL语句中问号的值的元组;bindings允许绑定数据处理程序;maxrows指定返回的最大行数,零或None表示无限制。 |
| raw(sql, params, bindings) | 执行sql语句并返回一个包含表头和结果的元组,params和bindings参数与isql方法相同。 |
| schema(table, full, sort) | 显示表的索引、外键、主键和列。如果full参数不为零,则结果包括表的引用键;sort值不为零表示表的列名将被排序。 |
| table(table) | table参数可选。如果没有提供表参数,此方法显示所有表的列表;否则,显示指定表的列。 |
| proc(proc) | proc参数可选。如果没有提供该参数,列出所有过程;否则,显示指定过程的参数。 |
| bcp(src, table, where=’(1=1)’) | 从指定的数据库和表(src和table)复制数据到当前实例的数据库。 |
| begin(self) | 创建一个新的游标。 |
| rollback(self) | 回滚自创建新游标以来执行的语句。 |
| commit(self, cursor=None, maxrows=None) | 提交自创建新游标以来执行的语句。 |
| display(self) | 使用当前格式化程序显示结果。 |
总结
Jython在数据库编程领域提供了丰富的功能和多样的工具。从DBM文件的简单使用,到对象的序列化存储,再到与不同数据库管理系统(如MySQL和PostgreSQL)的交互,以及使用JDBC和zxJDBC进行高效的数据库操作,Jython都展现出了强大的能力。通过对这些知识的掌握,开发者可以根据具体需求选择合适的方法和工具,提高数据库编程的效率和质量,更好地完成各种数据库相关的开发任务。
为了更清晰地展示Jython数据库编程的整体流程,以下是一个mermaid流程图:
graph LR
A[选择数据库类型] --> B{MySQL或PostgreSQL}
B -- MySQL --> C[安装MySQL及JDBC驱动]
B -- PostgreSQL --> D[安装PostgreSQL及JDBC驱动]
C --> E[配置数据库连接信息]
D --> E
E --> F[选择连接方式]
F -- JDBC --> G[使用JDBC API连接]
F -- zxJDBC --> H[使用zxJDBC连接]
G --> I[执行SQL操作]
H --> I
I --> J[处理结果集]
J --> K[处理异常和警告]
K --> L[关闭连接]
这个流程图展示了从选择数据库类型到最终关闭连接的整个过程,涵盖了Jython数据库编程的主要步骤。希望通过本文的介绍,读者能够对Jython数据库编程有更深入的理解和掌握。
超级会员免费看
869

被折叠的 条评论
为什么被折叠?



