第 12 章 - 数据库和 Jython:对象关系映射和使用 JDBC¶
首先,我们将介绍 zxJDBC 包,它是 Jython 2.1 版本以来的标准部分,并符合 Python 2.0 DBI 标准。zxJDBC 对于简单的单次脚本来说是一个合适的选择,在这种情况下,数据库的可移植性不是问题。此外,在为 SQLAlchemy 或 Django 编写新的方言时,通常需要使用 zxJDBC。[但这并不完全正确:您可以使用 pg8000,这是一个纯 Python DBI 驱动程序,当然也可以编写自己的 DBI 驱动程序。但请不要这样做。] 因此,了解 zxJDBC 的工作原理在使用这些包时会很有用。但是,它对于更通用的使用来说过于底层。如果可能,请使用 SQLAlchemy 或 Django。最后,JDBC 本身也可以直接访问,就像 Jython 中的任何其他 Java 包一样。只需使用 java.sql 包即可。在实践中,这应该很少有必要。
本章的第二部分将重点介绍在 Jython 中使用对象关系映射。Jython 2.5 的发布为对象关系映射提供了许多新的选择。在本章中,我们将重点介绍在 Jython 中使用 SQLAlchemy,以及使用 Java 技术(如 Hibernate)。最后,您应该有几个不同的选择,可以在您的 Jython 应用程序中使用对象关系映射。
zxJDBC – 通过 JDBC 使用 Python 的 DB API¶
zxJDBC 简介¶
zxJDBC 包为 JDBC 提供了一个易于使用的 Python 包装器。zxJDBC 连接了两个标准
- JDBC 是 Java 中数据库访问的标准平台。
- DBI 是 Python 应用程序的标准数据库 API。
zxJDBC 是 Jython 的一部分,它为 JDBC 提供了一个符合 DBI 2.0 标准的接口。JDBC 有超过 200 个驱动程序可用 [ http://developers.sun.com/product/jdbc/drivers ],它们都可以与 zxJDBC 一起使用。所有主要关系数据库(包括 DB2、Derby、MySQL、Oracle、PostgreSQL、SQLite、SQLServer 和 Sybase)都提供了高性能驱动程序。此外,还为非关系型数据库和专用数据库提供了驱动程序。
但是,与 JDBC 不同,zxJDBC 在以最简单的方式使用时,可以阻止 SQL 注入攻击,最大限度地减少开销,并避免资源耗尽。此外,zxJDBC 默认使用事务模型(如果可用),而不是自动提交。
首先,我们将介绍连接和游标,它们是使用 zxJDBC 的关键资源,就像任何其他 DBI 包一样。然后,我们将介绍如何使用它们,包括典型的查询和数据操作事务。
入门¶
开发使用数据库后端的应用程序的第一步是确定应用程序将使用哪些数据库。在使用 zxJDBC 或其他 JDBC 实现的情况下,确定应用程序将使用哪个数据库对于整个开发过程至关重要。许多应用程序开发人员会选择使用对象关系映射器,正是出于这个原因。当应用程序使用 JDBC 实现进行编码,而 SQL 代码是手工编码时,指定的数据库选择会导致使用不同的 SQL 方言。对象关系映射 (ORM) 技术的优势之一是 SQL 对开发人员是透明的。ORM 技术在幕后处理不同的方言。这也是 ORM 技术在实现对许多不同数据库的支持方面可能比较慢的原因之一。以 SQLAlchemy 或 Django 为例,这些技术中的每一种都必须为每个数据库编码不同的方言。使用 ORM 可以使应用程序在许多不同的数据库上更具可移植性。但是,正如前言中所述,如果您的应用程序只针对一两个数据库,那么使用 zxJDBC 将是一个不错的选择。
在使用 Java 的 JDBC 时,必须处理查找和注册数据库驱动程序的任务。大多数主要数据库都提供其 JDBC 驱动程序供使用。其他一些可能要求您在下载驱动程序之前注册,或者在某些情况下购买它。由于 zxJDBC 是 JDBC 的替代实现,因此必须使用 JDBC 驱动程序才能使用该 API。大多数 JDBC 驱动程序以 JAR 文件的形式提供,可以安装到应用程序服务器容器和 IDE 中。为了使用特定的数据库驱动程序,它必须位于 CLASSPATH 中。如前所述,要查找特定数据库的给定 JDBC 驱动程序,请查看 Sun Microsystems JDBC 驱动程序搜索页面 (http://developers.sun.com/product/jdbc/drivers),因为它包含了当今大多数数据库的不同 JDBC 驱动程序的列表。
注意:本节中的示例适用于 Jython 2.5.1 及更高版本。Jython 2.5.1 引入了一些简化操作,用于处理连接和游标。此外,我们假设大多数示例使用 PostgreSQL,并使用 world 示例数据库(也适用于 MySQL)。为了能够按照以下各节中的示例操作,您应该有一个包含 world 数据库示例的 PostgreSQL 数据库。请访问 PostgreSQL 主页 http://postgresql.ac.cn 下载数据库。world 数据库示例与本书的源代码一起提供。可以通过打开 psql 并执行以下命令将其安装到 PostgreSQL 数据库中
postgres=# \i <path to world sql>/world.sql
如前所述,获得驱动程序后,必须将其放入类路径中。以下是将 JDBC 驱动程序添加到几个最流行数据库的 CLASSPATH 中的一些示例。
# Oracle
# Windows
set CLASSPATH=<PATH TO JDBC>\ojdbc14.jar;%CLASSPATH%
# OS X
export CLASSPATH=<PATH TO JDBC>/ojdbc14.jar:$CLASSPATH
# PostgreSQL
# Windows
set CLASSPATH=<PATH TO JDBC>\postgresql-x.x.jdbc4.jar;%CLASSPATH%
# OS X
export CLASSPATH<PATH TO JDBC>/postgresql-x.x.jdbc4.jar:$CLASSPATH
将目标数据库的适当 JAR 添加到 CLASSPATH 后,就可以开始开发了。需要注意的是,zxJDBC(以及所有其他 JDBC 实现)使用类似的过程来处理数据库。必须执行以下任务才能使用 JDBC 实现
- 创建连接
- 创建查询或语句
- 获取查询或语句的结果
- 如果使用查询,则在游标中获取结果并迭代数据以执行任务
- 关闭游标
- 关闭连接(如果在 Jython 2.5.1 之前的版本中未使用 with_statement 语法)
在接下来的几节中,我们将研究这些步骤中的每一个,以及 zxJDBC 如何使它们比直接使用 JDBC 更容易。
连接¶
数据库连接只是一个资源对象,它管理对数据库系统的访问。由于数据库资源通常是分配成本很高的对象,并且很容易耗尽,因此在您完成使用后立即关闭它们非常重要。有两种方法可以创建数据库连接
- 直接创建。独立代码(例如脚本)将直接创建连接。
- JNDI。容器管理的代码应使用 JNDI 来创建连接。此类容器包括 GlassFish、JBoss、Tomcat、WebLogic 和 WebSphere。通常,连接在在此上下文中运行时会被池化,并且与给定的安全上下文相关联。
以下是使用 Jython 2.5.1 在托管容器之外创建数据库连接的最佳方法示例。需要注意的是,在 2.5.1 之前,with_statement 语法不可用。这是由于在 2.5.1 之前的 Jython 版本中,PyConnection 的底层实现导致的。一般来说,任何可以通过 with_statement 使用的对象都必须实现某些功能,包括 __exit__ 方法。请参阅下面的说明,了解如何在 2.5.1 之前的版本中实现此功能。另一个需要注意的是,为了连接,我们必须使用符合特定数据库标准的 JDBC url…在本例中为 PostgreSQL。
from __future__ import with_statement
from com.ziclix.python.sql import zxJDBC
# for example
jdbc_url = "jdbc:postgresql:test"
username = "postgres"
password = "jython25"
driver = "org.postgresql.Driver"
with zxJDBC.connect(jdbc_url, username, password, driver) as conn:
do_something(conn)
# there is no need to close the cursor as the with statement takes care of this for us
逐步讲解,您可以看到我们导入了 with_statement 和 zxJDBC,因为我们将使用它们来获取连接。下一步是定义一系列将在连接活动中使用的字符串值。请注意,如果将它们设置为全局变量,则只需要定义一次。最后,获取连接并执行一些操作。现在让我们看一下用 Java 编写的相同过程,以便进行比较。
import java.sql.*;
import org.postgresql.Driver;
...
// In some method
Connection conn = null;
String jdbc_url = "jdbc:postgresql:test";
String username = "postgres";
String password = "jython25";
String driver = "org.postgresql.Driver";
try {
DriverManager.registerDriver(new org.postgresql.Driver());
conn = DriverManager.getConnection(jdbc_url,
username, password);
// do something using statement and resultset
conn.close();
}
catch(Exception e) {
logWriter.error("getBeanConnection ERROR: ",e);
return conn;
}
注意
在 2.5.1 之前的 Jython 版本中,with_statement 语法不可用。因此,我们必须直接使用连接(即在完成时关闭它)。请查看以下代码,了解如何在没有 with_statement 功能的情况下使用 zxJDBC 连接。
from __future__ import with_statement from com.ziclix.python.sql import zxJDBC
# 例如 jdbc_url = “jdbc:postgresql:test” username = “postgres” password = “jython25” driver = “org.postgresql.Driver”
conn = zxJDBC.connect(jdbc_url, username, password, driver) do_something(conn) # 确保通过关闭连接(和游标)来清理 conn.close()
with
语句确保在工作完成后立即关闭连接。另一种方法是使用 finally
来执行关闭操作。使用后一种技术可以实现更严格的异常处理技术,但也增加了大量的代码。如前所述,with
语句在 2.5.1 之前的 Jython 版本中不可用,因此在使用这些版本时,建议使用这种方法。
try:
conn = zxJDBC.connect(jdbc_url, username, password, driver)
do_something(conn)
finally:
conn.close()
zxJDBC 中的连接 (PyConnection) 对象具有许多方法和属性,可用于执行各种功能和获取元数据信息。例如,close 方法可用于关闭连接。以下表格列出了连接的所有可用方法和属性及其功能。
表 12-1:连接方法
方法 | 功能 |
---|---|
close | 立即关闭连接(而不是在调用 __del__ 时关闭)。 |
commit | 提交对连接执行的所有操作。 |
cursor | 从连接返回一个新的游标对象。 |
rollback | 如果数据库提供事务,则此方法会导致数据库回滚到任何挂起事务的开始。 |
nativesql | 将给定的 SQL 语句转换为系统的本机 SQL 语法。 |
表 12-2:连接属性
属性 | 功能 |
---|---|
autocommit | 启用或禁用连接上的自动提交。默认情况下禁用。 |
dbname | 返回数据库的名称。 |
dbversion | 返回数据库的版本。 |
drivername | 返回数据库驱动程序的名称。 |
driverversion | 返回数据库驱动程序的版本。 |
url | 返回正在使用的数据库 URL。 |
__connection__ | 返回正在使用的连接类型。 |
__cursors__ | 返回连接上所有打开的游标的列表。 |
__statements__ | 返回连接上所有打开的语句的列表。 |
closed | 返回一个布尔值,表示连接是否已关闭。 |
当然,我们始终可以使用连接来获取所有方法和属性的列表,方法是使用以下语法。
>>> conn.__methods__
['close', 'commit', 'cursor', 'rollback', 'nativesql']
>>> conn.__members__
['autocommit', 'dbname', 'dbversion', 'drivername', 'driverversion', 'url', '__connection__', '__cursors__', '__statements__', 'closed']
注意
连接池通过提供连接的重用并确保连接实际上有效,有助于确保更强大的操作。通常,简单的代码会长时间保持连接,以避免创建连接的开销,然后会费力地管理在网络或服务器故障时重新连接。最好让连接池基础设施来管理这些操作,而不是重新发明轮子。
所有事务,如果支持,都在连接的上下文中进行。我们将在数据修改的小节中进一步讨论事务,但这是基本方法。
try:
# Obtain a connection that is not using auto-commit (default for zxJDBC)
conn = zxJDBC.connect(jdbc_url, username, password, driver)
# Perform all work on connection
do_something(conn)
# After all work is complete, commit
conn.commit
except:
# If a failure occurs along the way, rollback all previous work
conn.rollback()
zxJDBC.lookup¶
在托管容器中,您将使用 zxJDBC.lookup
而不是 zxJDBC.connect
。如果您有需要在容器内外运行的代码,我们建议您使用工厂来抽象化此操作。在容器内部,例如应用程序服务器,您应该使用 JDNI 来分配资源。通常,连接将由连接池管理。
factory = "com.sun.jndi.fscontext.RefFSContextFactory"
db = zxJDBC.lookup('jdbc/postgresDS'),
INITIAL_CONTEXT_FACTORY=factory)
上面的示例假设容器中定义的数据源名为“jdbc/postgresDS”,并且它使用 Sun FileSystem JNDI 参考实现。此查找过程不需要知道 JDBC URL 或驱动程序工厂类。这些方面,以及可能的用户名称和密码,由容器管理员使用特定于该容器的工具配置。通常,按照惯例,您会发现 JNDI 名称通常类似于 *jdbc/NAME* 格式。
游标¶
获得连接后,您可能想用它做些事情。由于您可以在事务中执行多个操作 - 查询一个表,更新另一个表 - 您需要另一个资源,即游标。zxJDBC 中的游标是 JDBC 语句和 resultSet 对象的包装器,它提供了一种非常 *Pythonic* 的语法来与数据库交互。结果是一个易于使用且极其灵活的 API。游标用于保存从数据库获取的数据,它们可以以多种方式使用,我们将在后面讨论。有两种类型的游标可供使用,静态游标和动态游标。静态游标是默认类型,它基本上一次执行整个 resultSet 的迭代。后者的动态游标被称为延迟游标,它只在需要时才迭代 resultSet。以下是一些创建每种类型游标的示例。
# Assume that necessary imports have been performed
# and that a connection has been obtained and assigned
# to a variable 'conn'
cursor = conn.cursor() # static cursor creation
cursor = conn.cursor(1) # dynamic cursor creation with the boolean argument
动态游标由于内存限制而往往性能更好,但是,在某些情况下,它们不像使用静态游标那样方便。例如,如果您想查询数据库以查找行数,使用静态游标非常容易,因为所有行都将立即获取。这在动态游标中是不可能的,必须执行两个查询才能获得相同的结果。
# Using a static cursor to obtain rowcount
>>> cursor = conn.cursor()
>>> cursor.execute("select * from country")
>>> cursor.rowcount
239
# Using a dynamic cursor to obtain rowcount
>>> cursor = conn.cursor(1)
>>> cursor.execute("select * from country")
>>> cursor.rowcount
0
# Since rowcount does not work with dynamic, we must
# perform a separate count query to obtain information
>>> cursor.execute("select count(*) from country")
>>> cursor.fetchone()
(239L,)
游标用于执行查询、插入、更新、删除和/或发出数据库命令。与连接一样,游标具有一系列方法和属性,可用于执行操作或获取元数据信息。
表 12-3:游标方法
方法 | 功能 |
---|---|
tables | 检索表列表。(目录、模式模式、表模式、类型) |
columns | 检索列列表。(目录、模式模式、表名模式、列名模式) |
primarykeys | 检索主键列表。(目录、模式、表) |
foreignkeys | 检索外键列表。(主键目录、主键模式、主键表、外键目录、外键模式、外键表) |
procedures | 检索过程列表。(目录、模式、表) |
procedurecolumns | 检索过程列列表。(目录、模式模式、过程模式、列模式) |
statistics | 获取查询的统计信息。(目录、模式、表、唯一、近似值) |
bestrow | 唯一标识行的最佳列集 |
versioncolumns | 当行中的任何值更新时自动更新的列 |
close | 关闭游标 |
execute | 执行游标中包含的代码 |
executemany | 用于执行准备好的语句或带有参数列表的 sql |
fetchone | 获取查询结果集的下一行,返回单个序列,如果不存在更多数据则返回 None |
fetchall | 获取查询结果的所有(剩余)行,并将它们作为序列的序列返回 |
fetchmany | 获取查询结果的下一组行,返回一个序列的序列 |
callproc | 执行存储过程 |
next | 移动到游标中的下一行 |
write | 执行写入此类文件对象的 sql |
表 12-4:游标属性
属性 | 功能 |
---|---|
arraysize | fetchmany() 在没有任何参数的情况下应返回的行数 |
rowcount | 返回结果行的数量 |
rownumber | 返回当前行号 |
description | 返回有关查询中每列的信息 |
datahandler | 返回指定的数据处理程序 |
warnings | 返回游标上的所有警告 |
lastrowid | 返回最后获取行的 rowid |
updatecount | 返回当前游标执行的更新次数 |
closed | 返回一个布尔值,表示游标是否已关闭 |
connection | 返回包含游标的连接对象 |
在游标使用某种查询或语句执行之前,无法使用上述方法和属性中的许多。大多数情况下,特定方法或属性名称将提供对其功能的足够描述。
创建和执行查询¶
如您之前所见,对给定游标启动查询非常容易。只需以字符串格式提供一个select语句作为游标execute()或executemany()方法的参数,然后使用其中一个fetch方法来迭代返回的结果。在以下示例中,我们查询 world 数据并通过关联的属性和方法显示一些游标数据。
>>> cursor = conn.cursor()
>>> cursor.execute("select country, region from country")
# Fetch next record
>>> cursor.fetchone()
((AFG,Afghanistan,Asia,"Southern and Central Asia",652090,1919,22720000,45.9,5976.00,,Afganistan/Afqanestan,"Islamic Emirate","Mohammad Omar",1,AF), u'Southern and Central Asia')
# Calling fetchmany() without any parameters returns next record
>>> cursor.fetchmany()
[((NLD,Netherlands,Europe,"Western Europe",41526,1581,15864000,78.3,371362.00,360478.00,Nederland,"Constitutional Monarchy",Beatrix,5,NL), u'Western Europe')]
# Fetch the next two records
>>> cursor.fetchmany(2)
[((ANT,"Netherlands Antilles","North America",Caribbean,800,,217000,74.7,1941.00,,"Nederlandse Antillen","Nonmetropolitan Territory of The Netherlands",Beatrix,33,AN), u'Caribbean'), ((ALB,Albania,Europe,"Southern Europe",28748,1912,3401200,71.6,3205.00,2500.00,Shqip?ria,Republic,"Rexhep Mejdani",34,AL), u'Southern Europe')]
# Calling fetchall() would retrieve the rest of the records
>>> cursor.fetchall()
...
# Using description provides data regarding the query in the cursor
>>> cursor.description
[('country', 1111, 2147483647, None, None, None, 2), ('region', 12, 2147483647, None, None, None, 0)]
使用 with_statment 语法创建游标很容易,请查看以下适用于 Jython 2.5.1 及更高版本的示例。
with conn.cursor() as c:
do_some_work(c)
与连接一样,您需要确保资源已正确关闭。当然,如果您从 shell 使用 Jython,通常无需担心资源分配。因此,您可以执行以下操作以遵循我们将要查看的较短示例
>>> c = conn.cursor()
>>> # work with cursor
如您所见,使用游标很容易处理查询。在上面的示例中,我们使用fetchall()方法检索查询的所有结果。但是,对于不需要所有结果的情况,还有其他选项可用,包括fetchone()和fetchmany()选项。有时,最好迭代查询的结果,以便分别处理每个记录。以下示例迭代 country 表中包含的国家/地区。
>>> from com.ziclix.python.sql import zxJDBC
>>> conn = zxJDBC.connect("jdbc:postgresql:test","postgres","jython25","org.postgresql.Driver")
>>> cursor = conn.cursor()
>>> cursor.execute("select name from country")
>>> while cursor.next():
... print cursor.fetchone()
...
(u'Netherlands Antilles',)
(u'Algeria',)
(u'Andorra',)
...
通常,查询不是硬编码的,我们需要能够在查询中替换值以选择应用程序所需的数据。开发人员有时还需要一种方法来创建动态 SQL 语句。当然,执行这些操作有多种方法。替换变量或创建动态查询的最简单方法是简单地使用字符串连接。毕竟,execute()方法接受基于字符串的查询。以下示例展示了如何使用字符串连接来动态地形成查询以及替换变量。
# Assume that the user selected a pull-down menu choice determining
# what results to retrive from the database, either continent or country name.
# The selected choice is stored in the selectedChoice variable. Let's also assume
# that we are intereted in all continents or countries beginning with the letter "A"
>>> qry = "select " + selectedChoice + " from country where " + selectedChoice + " like 'A%'"
>>> cursor.execute(qry)
>>> while cursor.next():
... print cursor.fetchone()
...
(u'Albania',)
(u'American Samoa',)
...
此技术非常适合创建动态查询,但也存在一些问题。例如,阅读串联的代码字符串可能会让眼睛感到麻烦。维护此类代码是一项繁琐的任务。除此之外,字符串连接并不是构建查询最安全的方法,因为它会使应用程序容易受到 SQL 注入攻击。SQL 注入是一种技术,用于将不希望的 SQL 代码传递到应用程序,从而以改变查询以执行不希望的操作的方式。如果用户能够在文本字段中键入自由文本并将该文本传递到字符串连接的查询中,最好执行其他一些过滤方法以确保某些关键字或注释符号不包含在值中。解决这些问题的更好方法是使用预处理语句。
注意
理想情况下,永远不要直接从用户数据构建查询语句。SQL 注入攻击利用这种构造作为其攻击媒介。即使不是恶意的,用户数据通常也会包含诸如引号之类的字符,如果未正确转义,会导致查询失败。在所有情况下,在将用户数据用于查询之前,务必对其进行清理,然后进行转义。
另一个考虑因素是,除非数据库语句缓存能够匹配(如果有的话),否则此类查询通常会消耗更多资源。
但我们的建议有两个重要的例外
- SQL 语句要求。绑定变量不能在所有地方使用。但是,具体情况将取决于数据库。
- 临时或不具代表性的查询。在像 Oracle 这样的数据库中,语句缓存将缓存执行计划,而不会考虑索引值的偏斜分布 - 但如果这些值以文字形式提供,数据库会知道。在这些情况下,如果将值直接放入语句中,将产生更有效的执行计划。
但是,即使在这些特殊情况下,也必须彻底清理所有用户数据。一个好的解决方案是使用某种映射表,无论是内部字典还是从数据库本身驱动。在某些情况下,精心构造的正则表达式也可能有效。小心。
预处理语句¶
为了避免使用字符串连接技术来替换变量,我们可以使用一种称为预处理语句的技术。预处理语句允许使用绑定变量进行数据替换,并且它们通常更安全,因为大多数安全考虑因素无需开发人员干预即可解决。但是,始终过滤输入以帮助降低风险是一个好主意。zxJDBC 中的预处理语句与 JDBC 中的工作方式相同,只是语法更简单。在下面的示例中,我们将使用预处理语句对 country 表执行查询。请注意,问号用作替换变量的占位符。同样重要的是要注意,在使用预处理语句时会调用executemany() 方法。传递到预处理语句中的任何替换变量都必须采用元组或列表的形式。
# Passing a string value into the query
qry = "select continent from country where name = ?"
>>> cursor.executemany(qry,['Austria'])
>>> cursor.fetchall()
[(u'Europe',)]
# Passing some variables into the query
>>> continent1 = 'Asia'
>>> continent2 = 'Africa'
>>> qry = "select name from country where continent in (?,?)"
>>> cursor.executemany(qry, [continent1, continent2])
>>> cursor.fetchall()
[(u'Afghanistan',), (u'Algeria',), (u'Angola',), (u'United Arab Emirates',), (u'Armenia',), (u'Azerbaijan',),
...
资源管理¶
您应该始终关闭连接和游标。这不仅是良好的做法,而且在托管容器中绝对必要,以避免耗尽相应的连接池,该池需要在连接不再使用时立即将其返回。 with
语句使它变得容易
from __future__ import with_statement
from itertools import islice
from com.ziclix.python.sql import zxJDBC
# externalize
jdbc_url = "jdbc:oracle:thin:@host:port:sid"
username = "world"
password = "world"
driver = "oracle.jdbc.driver.OracleDriver"
with zxJDBC.connect(jdbc_url, username, password, driver) as conn:
with conn.cursor() as c:
c.execute("select * from emp")
for row in islice(c, 20):
print row # let's redo this w/ namedtuple momentarily...
旧的替代方法可用。它更冗长,类似于通常必须编写的 Java 代码以确保关闭资源。
try:
conn = zxJDBC.connect(jdbc_url, username, password, driver)
cursor = conn.cursor()
#do something with the cursor
# Be sure to clean up by closing the connection (and cursor)
finally:
cursor.close()
conn.close()
元数据¶
如本章前面所述,可以通过使用某些属性来获取元数据信息,这些属性可用于连接和游标对象。zxJDBC 将这些属性与 JDBC java.sql.DatabaseMetaData 对象中找到的属性匹配。因此,当调用其中一个属性时,JDBC DatabaseMetaData 对象实际上是在获取信息。
以下示例显示了如何检索有关连接、游标甚至特定查询的元数据。请注意,在获取有关游标的元数据时,必须在设置属性后获取数据。
# Obtain information about the connection using connection attributes
>>> conn.dbname
'PostgreSQL'
>>> conn.dbversion
'8.4.0'
>>> conn.drivername
'PostgreSQL Native Driver'
# Check for existing cursors
>>> conn.__cursors__
[<PyExtendedCursor object instance at 1>]
# Obtain information about the cursor and the query
>>> cursor = conn.cursor()
# List all tables
>>> cursor.tables(None, None, '%', ('TABLE',))
>>> cursor.fetchall()
[(None, u'public', u'city', u'TABLE', None), (None, u'public', u'country', u'TABLE', None), (None, u'public', u'countrylanguage', u'TABLE', None), (None, u'public', u'test', u'TABLE', None)]
数据操作语言和数据定义语言¶
任何将操作数据库中包含的数据的应用程序都必须能够发出数据操作语言 (DML)。当然,DML 包括发出 INSERT、UPDATE 和 DELETE 等语句……CRUD 编程的基础。zxJDBC 使在标准游标对象中使用 DML 变得相当容易。这样做时,游标将返回一个值以提供有关结果的信息。JDBC 中的标准 DML 事务使用带有游标对象的预处理语句,并将结果分配给一个变量,该变量可以在之后读取以确定语句是否成功。
zxJDBC 还使用游标来使用数据定义语言 (DDL) 定义数据库中的新结构。执行此类操作的示例包括创建表、更改表、创建索引等。与使用 zxJDBC 执行 DML 类似,生成的 DDL 语句返回一个值以帮助确定语句是否成功。
在接下来的几个示例中,我们将创建一个表,插入一些值,删除值,最后删除表。
# Create a table named PYTHON_IMPLEMENTATIONS
>>> stmt = "create table python_implementations (id integer, python_implementation varchar, current_version varchar)"
>>> result = cursor.execute(stmt)
>>> print result
None
>>> cursor.tables(None, None, '%', ('TABLE',))
# Ensure table was created
>>> cursor.fetchall()
[(None, u'public', u'city', u'TABLE', None), (None, u'public', u'country', u'TABLE', None), (None, u'public', u'countrylanguage', u'TABLE', None), (None, u'public', u'python_implementations', u'TABLE', None), (None, u'public', u'test', u'TABLE', None)]
# Insert some values into the table
>>> stmt = "insert into PYTHON_IMPLEMENTATIONS values (?, ?, ?)"
>>> result = cursor.executemany(stmt, [1,'Jython','2.5.1'])
>>> result = cursor.executemany(stmt, [2,'CPython','3.1.1'])
>>> result = cursor.executemany(stmt, [3,'IronPython','2.0.2'])
>>> result = cursor.executemany(stmt, [4,'PyPy','1.1'])
>>> conn.commit()
# Query the database
>>> cursor.execute("select python_implementation, current_version from python_implementations")
>>> cursor.rowcount
4
>>> cursor.fetchall()
[(u'Jython', u'2.5.1'), (u'CPython', u'3.1.1'), (u'IronPython', u'2.0.2'), (u'PyPy', u'1.1')]
# Update values and re-query
>>> stmt = "update python_implementations set python_implementation = 'CPython -Standard Implementation' where id = 2"
>>> result = cursor.execute(stmt)
>>> print result
None
>>> conn.commit()
>>> cursor.execute("select python_implementation, current_version from python_implementations")
>>> cursor.fetchall()
[(u'Jython', u'2.5.1'), (u'IronPython', u'2.0.2'), (u'PyPy', u'1.1'), (u'CPython -Standard Implementation', u'3.1.1')]
使用批量插入和更新是一个好习惯。每次提交都会产生性能损失。如果将 DML 语句分组在一起,然后进行提交,则生成的交易将执行得更好。使用批量 DML 语句的另一个好理由是确保事务安全。如果事务中的一个语句失败,则所有其他语句都应该回滚。如本章前面所述,使用 try/except 子句将维护事务依赖关系。如果一个语句失败,则所有其他语句都将回滚。同样,如果它们都成功,则它们将使用最终的提交提交到数据库。
调用过程¶
数据库应用程序通常使用存在于数据库中的过程和函数。这些过程通常用 SQL 过程语言编写,例如 Oracle 的 PL/SQL 或 PostgreSQL 的 PL/pgSQL。编写数据库过程并在 Python、Java 等外部应用程序中使用它们非常有意义,因为过程通常是处理数据的最简单方法。它们不仅运行在金属附近,因为它们在数据库中,而且它们也比需要连接和关闭数据库连接的 Jython 应用程序快得多。由于过程存在于数据库中,因此连接的建立不会产生性能损失。
zxJDBC 可以像 JDBC 一样轻松地调用数据库过程。这有助于开发人员创建一些更以数据库为中心的代码驻留在数据库中的过程,以及在应用程序服务器上运行并与数据库无缝交互的其他特定于应用程序的代码的应用程序。为了调用数据库过程,zxJDBC 提供了 callproc() 方法,该方法接受要调用的过程的名称。在以下示例中,我们创建了一个相对无用的过程,然后使用 Jython 调用它。
PostgreSQL 过程
CREATE OR REPLACE FUNCTION proc_test(
OUT out_parameter CHAR VARYING(25) )
AS $$
DECLARE
BEGIN
SELECT python_implementation
INTO out_parameter
FROM python_implementations
WHERE id = 1;
RETURN;
END;
$$ LANGUAGE plpgsql;
Jython 调用代码
>>> result = cursor.callproc('proc_test')
>>> cursor.fetchall()
[(u'Jython',)]
虽然这个例子比较简单,但很容易看出 zxJDBC 中使用数据库过程如何变得重要。将数据库过程和函数与应用程序代码结合使用是一种强大的技术,但它确实将应用程序绑定到特定数据库,因此应该谨慎使用。
自定义 zxJDBC 调用¶
有时,能够自动更改或操作 SQL 语句很方便。这可以在语句发送到数据库之前、发送到数据库之后,甚至只是为了获取有关已发送语句的信息而完成。为了操作或自定义数据调用,可以使用 zxJDBC 提供的 DataHandler 接口。使用 DataHandler 时,基本上有三种不同的方法来处理类型映射。它们在过程中的不同时间被调用,一个在获取时,另一个在绑定对象以供准备好的语句使用时。这些数据类型映射回调分为四组:生命周期、开发人员支持、绑定准备好的语句和构建结果。
乍一看,自定义和操作语句似乎很复杂,甚至有点令人生畏。但是,zxJDBC DataHandler 使这项任务变得相当简单。只需创建一个处理程序类并通过覆盖给定的处理程序方法来实现所需的功能。以下是可以覆盖的各种方法的列表,我们将在后面看一个简单的示例。
生命周期
- public void preExecute(Statement stmt) throws SQLException;
- 在执行语句之前进行回调。如果语句是 PreparedStatement(在参数发送到 execute 方法时创建),则所有参数都将已设置。
- public void postExecute(Statement stmt) throws SQLException;
- 在成功执行语句后进行回调。这对于自动递增列等情况特别有用,在这种情况下,语句知道插入的值。
开发人员支持
- public String getMetaDataName(String name);
- 用于确定在 DatabaseMetaData 方法(例如 getTables())中使用的名称的正确大小写的回调。这对于期望所有名称都为大写的 Oracle 特别有用。
- public PyObject getRowId(Statement stmt) throws SQLException;
- 用于返回最后插入语句的行 ID 的回调函数。
绑定预处理语句
- public Object getJDBCObject(PyObject object, int type);
- 当通过使用 execute 方法创建 PreparedStatement 时,会调用此方法。当参数绑定到语句时,DataHandler 会获得一个回调函数来映射类型。仅当存在类型绑定时才会调用此方法。
- public Object getJDBCObject(PyObject object);
- 当在执行 PreparedStatement 时不存在类型绑定时,会调用此方法。
构建结果
- public PyObject getPyObject(ResultSet set, int col, int type);
- 从数据库中获取数据时会调用此方法。根据 JDBC 类型,从 ResultSet set 中列 col 的 Java 对象返回相应的 PyObject 子类。
- 现在我们将研究一个使用此技术的简单示例。该方法基本上遵循以下步骤
- 创建一个处理程序类来实现特定功能(必须实现 DataHandler 接口)
- 将创建的处理程序类分配给给定的游标对象
- 使用游标对象进行数据库调用
在以下示例中,我们覆盖了preExecute方法,以打印一条消息,说明该功能已更改。如您所见,这非常容易实现,并且打开了无数可能性。
PyHandler.py
from com.ziclix.python.sql import DataHandler
class PyHandler(DataHandler):
def __init__(self, handler):
self.handler = handler
print 'Inside DataHandler'
def getPyObject(self, set, col, datatype):
return self.handler.getPyObject(set, col, datatype)
def getJDBCObject(self, object, datatype):
print "handling prepared statement"
return self.handler.getJDBCObject(object, datatype)
def preExecute(self, stmt):
print "calling pre-execute to alter behavior"
return self.handler.preExecute(stmt)
Jython 解释器代码
>>> cursor.datahandler = PyHandler(cursor.datahandler)
Inside DataHandler
>>> cursor.execute("insert into test values (?,?)", [1,2])
calling pre-execute
历史记录¶
zxJDBC 由 Brian Zimmer 贡献,他是 Jython 的前首席提交者。编写此 API 的目的是使 Jython 开发人员能够使用更类似于 Python DB API 的技术来与数据库进行交互。该软件包最终成为 Jython 发行版的一部分,如今它是最重要的底层 API 之一,用于与 Django 等更高级别的框架进行交互。zxJDBC API 在本文发表时正在不断发展,并且很可能在未来的版本中变得更加有用。
对象关系映射¶
虽然 zxJDBC 确实为通过 Jython 访问数据库提供了一种可行的选择,但还有许多其他解决方案可用。如今,许多开发人员选择使用 ORM(对象关系映射)解决方案来与数据库进行交互。本节不是关于 ORM 的介绍,我们假设您至少对该主题有所了解。此外,即将讨论的 ORM 解决方案已经在网络或书籍格式中提供了大量非常好的文档。因此,本节将深入了解如何在 Jython 中使用这些技术,但不会详细介绍每个 ORM 解决方案的工作原理。话虽如此,毫无疑问,这些解决方案对于独立应用程序和企业应用程序都非常强大且功能强大。
在接下来的几节中,我们将介绍如何在 Jython 中使用一些当今最流行的 ORM 解决方案。您将学习如何设置环境以及如何编写 Jython 代码来与每个 ORM 进行交互。在本节结束时,您应该具备足够的知识来开始使用 Jython 使用这些 ORM,甚至开始构建 Jython ORM 应用程序。
SqlAlchemy¶
毫无疑问,SqlAlchemy 是 Python 编程语言中最广为人知和使用最广泛的 ORM 解决方案之一。它已经存在了足够长的时间,其成熟度和稳定性使其成为您应用程序中一个极好的竞争者。它易于设置,并且易于用于新数据库和旧数据库。您可以认真地下载并安装 SqlAlchemy,并在很短的时间内开始使用它。使用此解决方案的语法非常直观,并且与其他 ORM 技术一样,通过使用将特殊 Jython 类链接到数据库中特定表的映射器来与数据库实体进行交互。最终结果是,应用程序通过使用实体类而不是数据库 SQL 事务来持久化。
在本节中,我们将介绍 SqlAlhemy 在 Jython 中的安装和配置。然后,它将向您展示如何通过几个简短的示例开始使用它,我们不会详细介绍,因为已经有很多关于 SqlAlchemy 的优秀参考资料。但是,本节应该填补在 Jython 上使用此出色解决方案的空白。
安装¶
我们将从下载 SqlAlchemy 网站(http://sqlalchemy.org.cn)开始,在撰写本文时,应使用 0.6 版本。此版本已安装并使用 Jython 2.5.0 版本进行了测试。下载软件包后,将其解压缩到工作站上的目录中,然后在终端或命令提示符中导航到该目录。进入 SqlAlchemy 目录后,执行以下命令进行安装
jython setup.py install
完成此过程后,SqlAlchemy 应该已成功安装到您的 jython Libsite-packages 目录中。您现在可以从 jython 访问 SqlAlchemy 模块,并且可以打开终端并检查以确保安装成功,方法是导入 sqlalchemy 并检查版本。
>>> import sqlalchemy
>>> sqlalchemy.__version__
'0.6beta1'
>>>
确保安装成功后,就可以开始通过终端使用 SqlAlchemy 了。但是,在我们开始之前,还有一步需要完成。Jython 使用 zxJDBC 在 Java 中实现 Python 数据库 API。最终结果是,大多数可用于 SqlAlchemy 的方言无法开箱即用地与 Jython 配合使用。这是因为方言需要重新编写以实现 zxJDBC。在撰写本文时,我只能找到一个已完成的方言 zxoracle,它已重新编写以使用 zxJDBC,我将在接下来的部分中向您展示一些基于 zxoracle 的示例。但是,其他方言正在开发中,包括 SQLServer 和 MySQL。坏消息是 SqlAlchemy 尚未与所有可用的数据库配合使用,另一方面,Oracle 是一个非常好的起点,实现新的方言并不困难。您可以在本书的源代码中找到 zxoracle.py 方言。浏览它,您会发现为您的选择数据库实现类似的方言可能并不困难。您可以将 zxoracle 放在 Jython 路径上的某个位置,也可以将其放在 Jython 安装中的 Lib 目录中。
最后,我们需要确保我们的数据库 JDBC 驱动程序位于我们的路径上的某个位置,以便 Jython 可以访问它。完成本节中包含的步骤后,启动 jython 并使用下一节中的信息练习一些基本的 SqlAlchemy。
使用 SqlAlchemy¶
我们可以直接通过终端或命令行使用 SqlAlchemy。为了使用它,您需要遵循一组相对基本的步骤。首先,导入执行计划任务所需的模块。其次,创建一个引擎以在访问数据库时使用。第三,如果您尚未创建数据库表,请创建它们,并将它们映射到使用 SqlAlchemy 映射器的 Python 类。最后,开始使用数据库。
现在,就像任何其他技术一样,在这种技术中也有几种不同的方法可以做事情。例如,您可以遵循一个非常细粒度的过程来创建表、创建类和映射,这些过程涉及每个步骤的单独步骤,或者您可以使用称为声明性过程的方法,并同时执行所有这些任务。在本章中,我将向您展示如何执行这些操作,以及如何使用 SqlAlchemy 执行基本的数据库活动。如果您不熟悉 SqlAlchemy,我建议您通读本节,然后访问 sqlalchemy.org 并通读那里的大量文档库。但是,如果您已经熟悉 SqlAlchemy,则可以根据需要继续前进,因为本节的其余部分是 ORM 解决方案本身的基本教程。
我们的第一步是创建一个可用于数据库的引擎。创建引擎后,我们就可以开始执行使用它的数据库任务了。在您的终端中输入以下代码行,将数据库特定信息替换为开发数据库的详细信息。
>>> import zxoracle
>>> from sqlalchemy import create_engine
>>> db = create_engine('zxoracle://schema:password@hostname:port/database)
接下来,我们将创建使用 SqlAlchemy 创建数据库表所需的元数据。您可以通过元数据创建一张或多张表,它们实际上直到元数据应用于您的数据库引擎后才会创建,方法是在元数据上调用 create_all()。在本例中,我将引导您完成创建名为 Player 的表的步骤,该表将在下一节的应用程序示例中使用。
>>>player = Table('player', metadata,
... Column('id', Integer, primary_key=True),
... Column('first', String(50)),
... Column('last', String(50)),
... Column('position', String(30)))
>>> metadata.create_all(engine)
我们的表现在应该存在于数据库中,下一步是创建一个 Python 类来用于访问此表。
class Player(object):
def __init__(self, first, last, position):
self.first = first
self.last = last
self.position = position
def __repr__(self):
return "<Player('%s', '%s', '%s')>" %(self.first, self.last, self.position)
下一步是创建一个映射器,将 Player python 对象与 player 数据库表相关联。为此,我们使用 mapper() 函数创建一个新的 Mapper 对象,将类和表绑定在一起。然后,映射器函数将对象存储起来以供将来参考。
>>> from sqlalchemy.orm import mapper
>>> mapper(Player, player)
<Mapper at 0x4; Player>
创建映射器是设置环境以使用我们的表的最后一步。现在,让我们回顾一下以更简单的方式执行所有这些步骤。如果我们想一次创建表、类和映射器,那么我们可以声明式地完成。请注意,使用 Oracle 方言,我们需要使用序列来为表生成自动递增的 id 列。为此,导入 sqlalchemy.schema.Sequence 对象并在创建时将其传递给 id 列。您必须确保已在 Oracle 数据库中手动创建此序列,否则将无法正常工作。
SQL> create sequence id_seq
2 start with 1
3 increment by 1;
Sequence created.
# Delarative creation of the table, class, and mapper
>>> from sqlalchemy.ext.declarative import declarative_base
>>> from sqlalchemy.schema import Sequence
>>> Base = declarative_base()
>>> class Player(object):
... __tablename__ = 'player'
... id = Column(Integer, Sequence(‘id_seq’), primary_key=True)
... first = Column(String(50))
... last = Column(String(50))
... position = Column(String(30))
... def __init__(self, first, last, position):
... self.first = first
... self.last = last
... self.position = position
... def __repr__(self):
... return "<Player('%s','%s','%s')>" % (self.first, self.last, self.position)
...
现在是时候创建一个会话并开始使用我们的数据库了。我们必须创建一个会话类并将其绑定到我们之前使用 create_engine 定义的数据库引擎。创建后,Session 类将为我们的数据库创建新的会话对象。Session 类还可以执行本节范围之外的其他操作,但您可以在 sqlalchemy.org 或网络上其他优秀的参考资料中了解更多信息。
>>> from sqlalchemy.orm import sessionmaker
>>> Session = sessionmaker(bind=db)
我们现在可以开始创建 Player 对象并将它们保存到我们的会话中。这些对象将在需要时持久化到数据库中,这也称为 flush()。如果我们在会话中创建对象,然后查询它,sqlalchemy 将首先将对象持久化到数据库,然后执行查询。
#Import sqlalchemy module and zxoracle
>>> import zxoracle
>>> from sqlalchemy import create_engine
>>> from sqlalchemy import Table, Column, String, Integer, MetaData, ForeignKey
>>> from sqlalchemy.schema import Sequence
# Create engine
>>> db = create_engine('zxoracle://schema:password@hostname:port/database’)
# Create metadata and table
>>> metadata = MetaData()
>>> player = Table('player', metadata,
... Column('id', Integer, Sequence('id_seq'), primary_key=True),
... Column('first', String(50)),
... Column('last', String(50)),
... Column('position', String(30)))
>>> metadata.create_all(db)
# Create class to hold table object
>>> class Player(object):
... def __init__(self, first, last, position):
... self.first = first
... self.last = last
... self.position = position
... def __repr__(self):
... return "<Player('%s','%s','%s')>" % (self.first, self.last, self.position)
# Create mapper to map the table to the class
>>> from sqlalchemy.orm import mapper
>>> mapper(Player, player)
<Mapper at 0x4; Player>
# Create Session class and bind it to the database
>>> from sqlalchemy.orm import sessionmaker
>>> Session = sessionmaker(bind=db)
>>> session = Session()
# Create player objects, add them to the session
>>> player1 = Player('Josh', 'Juneau', 'forward')
>>> player2 = Player('Jim', 'Baker', 'forward')
>>> player3 = Player('Frank', 'Wierzbicki', 'defense')
>>> player4 = Player('Leo', 'Soto', 'defense')
>>> player5 = Player('Vic', 'Ng', 'center')
>>> session.add(player1)
>>> session.add(player2)
>>> session.add(player3)
>>> session.add(player4)
>>> session.add(player5)
# Query the objects
>>> forwards = session.query(Player).filter_by(position='forward').all()
>>> forwards
[<Player('Josh','Juneau','forward')>, <Player('Jim','Baker','forward')>]
>>> defensemen = session.query(Player).filter_by(position='defense').all()
>>> defensemen
[<Player('Frank','Wierzbicki','defense')>, <Player('Leo','Soto','defense')>]
>>> center = session.query(Player).filter_by(position='center').all()
>>> center
[<Player('Vic','Ng','center')>]
好吧,希望从这个例子中您可以看到使用 SqlAlchemy 的好处。当然,您可以执行所有必要的 SQL 操作,例如插入、更新、选择和删除对象。但是,如前所述,有很多非常好的教程可以教您如何执行这些操作。我们只是触及了 SqlAlchemy 功能的皮毛,它是一个非常强大的工具,可以添加到任何 Jython 或 Python 开发人员的工具库中。
Hibernate¶
Hibernate 是 Java 世界中非常流行的对象关系映射解决方案。事实上,它非常流行,以至于许多其他 ORM 解决方案要么使用 Hibernate,要么以各种方式扩展它。作为 Jython 开发人员,我们可以利用 Hibernate 创建强大的混合应用程序。由于 Hibernate 通过将 POJO(普通旧 Java 对象)类映射到数据库表来工作,因此我们无法将 Jython 对象直接映射到它。虽然我们始终可以尝试使用对象工厂将我们的 Jython 对象强制转换为 Hibernate 可以使用的格式,但这方法有点不尽如人意。因此,如果您希望创建一个完全使用 Jython 编写的应用程序,这可能不是最佳的 ORM 解决方案。但是,大多数 Jython 开发人员习惯于在 Java 中做一些工作,因此他们可以利用 Hibernate API 的成熟性和强大功能来创建一流的混合应用程序。本节将向您展示如何使用 Hibernate 和 Java 创建数据库持久性对象,然后直接从 Jython 应用程序中使用它们。最终结果是,在 Java 中编写实体 POJO,将它们与 Hibernate 和所有必需的映射文档一起放入 JAR 文件中,然后将 JAR 导入到您的 Jython 应用程序中并使用。
我发现创建此类应用程序最简单的方法是使用 Eclipse 或 Netbeans 等 IDE。然后创建两个独立的项目,其中一个项目将是一个纯 Java 应用程序,其中将包含实体 bean。另一个项目将是一个纯 Jython 应用程序,其中将包含所有其他内容。在这种情况下,您可以简单地将 Java 项目生成的 JAR 添加到 Jython 项目的 sys.path 中,您就可以开始使用。但是,如果您不想使用 IDE,这同样有效。
重要的是要注意,本节将为您提供一个使用 Jython、Java 和 Hibernate 协同工作的用例。可能还有许多其他场景,在这种场景中,这种技术组合同样有效,甚至更好。还需要注意的是,本节不会深入介绍 Hibernate;我们只会触及它功能的皮毛。如果您发现此解决方案有用,网络上有很多优秀的 Hibernate 教程。
实体类和 Hibernate 配置¶
由于我们的 Hibernate 实体 bean 必须用 Java 编写,因此大多数 Hibernate 配置将驻留在您的 Java 项目中。Hibernate 的工作方式很简单。您基本上将表映射到 POJO,并使用配置文件将两者映射在一起。也可以使用注释而不是 XML 配置文件,但出于本用例的目的,我将向您展示如何使用配置文件。
我们需要组装的第一个配置文件是 hibernate.cfg.xml,它位于 Java 项目的根目录下。该文件的作用是定义数据库连接信息,并声明项目中将使用哪些实体配置文件。在本例中,我们将使用 Postgresql 数据库,并使用我经典的曲棍球阵容应用程序示例之一。这使得用例非常简单,因为我们只处理一个表,即 Player 表。Hibernate 使得使用多个表甚至以各种方式关联它们变得非常容易。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.postgresql.Driver</property>
<property name="connection.url">jdbc:postgresql://127.0.0.1/database-name</property>
<property name="connection.username">username</property>
<property name="connection.password">password</property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<mapping resource="org/jythonbook/entity/Player.hbm.xml"/>
</session-factory>
</hibernate-configuration>
我们的下一步是为数据库表编写普通的 Java 对象。在本例中,我们将编写一个名为 Player 的对象,它只包含四个数据库列:id、first、last 和 position。正如您将看到的,我们在该类中使用标准的公共访问器方法和私有变量。
package org.jythonbook.entity;
public class Player {
public Player(){}
private long id;
private String first;
private String last;
private String position;
public long getId(){
return this.id;
}
private void setId(long id){
this.id = id;
}
public String getFirst(){
return this.first;
}
public void setFirst(String first){
this.first = first;
}
public String getLast(){
return this.last;
}
public void setLast(String last){
this.last = last;
}
public String getPosition(){
return this.position;
}
public void setPosition(String position){
this.position = position;
}
}
最后,我们将创建一个配置文件,Hibernate 将使用它将我们的 POJO 映射到数据库表本身。我们将确保使用 increment 类型的生成器类始终填充主键值。Hibernate 还允许使用其他生成器,包括序列(如果需要)。player.hbm.xml 文件应该放在与我们的 POJO 相同的包中,在本例中是 org.jythonbook.entity 包。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping
package="org.jythonbook.entity">
<class name="Player" table="player" lazy="true">
<comment>Player for Hockey Team</comment>
<id name="id" column="id">
<generator class="increment"/>
</id>
<property name="first" column="first"/>
<property name="last" column="last"/>
<property name="position" column="position"/>
</class>
</hibernate-mapping>
这就是我们在 Java 项目中为简单示例所需要做的全部工作。当然,您可以根据需要向自己的项目添加任意数量的实体类。要记住的主要一点是,所有实体类都用 Java 编写,我们将用 Jython 编写应用程序的其余部分。
使用 Java 实体类的 Jython 实现¶
我们用例的其余部分将用 Jython 编写。虽然所有 Hibernate 配置文件和实体类都在 Java 项目中编写并放置,但我们需要将该项目导入 Jython 项目,并导入 Hibernate JAR 文件,以便我们可以使用它的数据库会话和事务实用程序来处理实体。在 Netbeans 的情况下,您将创建一个 Python 应用程序,然后将 Python 平台设置为 Jython 2.5.0。之后,您应该将所有必需的 Hibernate JAR 文件以及 Java 项目 JAR 文件添加到项目属性中的 Python 路径。完成项目设置并处理完依赖项后,您就可以编写实现代码了。
如前所述,在本例中,我们正在编写一个曲棍球阵容实现。该应用程序在命令行上运行,基本上允许用户向阵容添加球员、删除球员和查看当前阵容。所有数据库事务都将使用我们在 Java 应用程序中编写的 Player 实体,并且我们将从 Jython 代码中使用 Hibernate 的事务管理。
# HockeyRoster.py
#
# Implemenatation logic for the HockeyRoster application
# Import Player class from the Player module
from org.hibernate.cfg import Environment
from org.hibernate.cfg import Configuration
from org.hibernate import Query
from org.hibernate import Session
from org.hibernate import SessionFactory
from org.hibernate import Transaction
from org.jythonbook.entity import Player
import sys
# Define a list to hold each of te Player objects
playerList = []
factory = None
# makeSelection()
#
# Creates a selector for our application. The function prints output to the
# command line. It then takes a parameter as keyboard input at the command line
# in order to choose our application option.
def makeSelection():
validOptions = ['1','2','3','4','5']
print "Please chose an option\n"
selection = raw_input("Press 1 to add a player, 2 to print the roster, 3 to search for a player on the team, 4 to remove player, 5 to quit: ")
if selection not in validOptions:
print "Not a valid option, please try again\n"
else:
if selection == '1':
addPlayer()
elif selection == '2':
printRoster()
elif selection == '3':
searchRoster()
elif selection == '4':
removePlayer()
else:
global runApp
runApp = False
print "Thanks for using the HockeyRoster application."
# addPlayer()
#
# Accepts keyboard input to add a player object to the roster list. This function
# creates a new player object each time it is invoked and appends it to the list.
def addPlayer():
addNew = 'Y'
print "Add a player to the roster by providing the following information\n"
while addNew.upper() == 'Y':
first = raw_input("First Name: ")
last = raw_input("Last Name: ")
position = raw_input("Position: ")
id = len(playerList)
session = factory.openSession()
try:
tx = session.beginTransaction()
player = Player()
player.first = first
player.last = last
player.position = position
session.save(player)
tx.commit()
except Exception,e:
if tx!=None:
tx.rollback()
print e
finally:
session.close()
# playerList.append(player)
print "Player successfully added to the roster\n"
addNew = raw_input("Add another? (Y or N)")
makeSelection()
# printRoster()
#
# Prints the contents of the list to the command line as a report
def printRoster():
print "====================\n"
print "Complete Team Roster\n"
print "======================\n\n"
playerList = returnPlayerList()
for player in playerList:
print "%s %s - %s" % (player.first, player.last, player.position)
print "\n"
print "=== End of Roster ===\n"
makeSelection()
# searchRoster()
#
# Takes input from the command line for a player's name to search within the
# roster list. If the player is found in the list then an affirmative message
# is printed. If not found, then a negative message is printed.
def searchRoster():
index = 0
found = False
print "Enter a player name below to search the team\n"
first = raw_input("First Name: ")
last = raw_input("Last Name: ")
position = None
playerList = returnPlayerList()
while index < len(playerList):
player = playerList[index]
if player.first.upper() == first.upper() and player.last.upper() == last.upper():
found = True
position = player.position
index = index + 1
if found:
print '%s %s is in the roster as %s' % (first, last, position)
else:
print '%s %s is not in the roster.' % (first, last)
makeSelection()
def removePlayer():
index = 0
found = False
print "Enter a player name below to remove them from the team roster\n"
first = raw_input("First Name: ")
last = raw_input("Last Name: ")
position = None
playerList = returnPlayerList()
foundPlayer = Player()
while index < len(playerList):
player = playerList[index]
if player.first.upper() == first.upper() and player.last.upper() == last.upper():
found = True
foundPlayer = player
index = index + 1
if found:
print '%s %s is in the roster as %s, are you sure you wish to remove?' % (foundPlayer.first, foundPlayer.last, foundPlayer.position)
yesno = raw_input("Y or N")
if yesno.upper() == 'Y':
session = factory.openSession()
try:
delQuery = "delete from Player player where id = %s" % (foundPlayer.id)
tx = session.beginTransaction()
q = session.createQuery(delQuery)
q.executeUpdate()
tx.commit()
print 'The player has been removed from the roster', foundPlayer.id
except Exception,e:
if tx!=None:
tx.rollback()
print e
finally:
session.close
else:
print 'The player will not be removed'
else:
print '%s %s is not in the roster.' % (first, last)
makeSelection()
def returnPlayerList():
session = factory.openSession()
try:
tx = session.beginTransaction()
playerList = session.createQuery("from Player").list()
tx.commit()
except Exception,e:
if tx!=None:
tx.rollback()
print e
finally:
session.close
return playerList
# main
#
# This is the application entry point. It simply prints the applicaion title
# to the command line and then invokes the makeSelection() function.
if __name__ == "__main__":
print "Hockey Roster Application\n\n"
cfg = Configuration().configure()
factory = cfg.buildSessionFactory()
global runApp
runApp = True
while runApp:
makeSelection()
我们在主块中开始我们的实现,加载 Hibernate 配置。所有 Hibernate 配置都位于 Java 项目中,因此我们这里不使用 XML,只是利用它。然后代码开始分支,以便可以执行各种任务。在将球员添加到阵容的情况下,用户可以在命令提示符下输入数字 1。您可以看到 addPlayer() 函数只是创建一个新的 Player 对象,填充它,然后将其保存到数据库中。同样,searchRoster() 函数调用另一个名为 returnPlayerList() 的函数,该函数使用 Hibernate 查询语言查询 player 表并返回一个 Player 对象列表。
最后,我们得到了一个完全可扩展的解决方案。我们可以使用成熟且广泛使用的 Java ORM 解决方案编写我们的实体,然后用 Jython 实现应用程序的其余部分。这使我们能够利用 Python 语言的最佳功能,但同时也能使用 Java 持久化我们的数据。
结论¶
如今,很难找到没有以某种形式使用关系型数据库的企业级应用程序。大多数现今使用的应用程序都使用数据库来存储信息,因为它们有助于提供强大的解决方案。话虽如此,本章中涵盖的主题对于任何开发人员来说都非常重要。在本章中,我们了解到在 Jython 中实现数据库应用程序有许多不同的方法,特别是通过 Java 数据库连接 API 或对象关系映射解决方案。