84、Jython数据库编程全解析

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 |
    • JDBC连接 :建立数据库连接的步骤如下:

      1. 将适当的驱动程序包含在类路径中。
      2. 向JDBC DriverManager注册驱动程序。
      3. 向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数据库编程有更深入的理解和掌握。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值