Zope使用一个对象数据库存储Zope对象。Oracle,Sybase,PostgreSQL等关系数据库使用一种不同的方法存储数据。关系数据库的数据存储在数据表中,如图10-1所示:
(图10-1 关系数据表)
表中的数据以“行”来存储。表的列组称为模式。标准查询语言SQL用来查找、修改关系数据库中的数据。
Zope不是用这种方法来存储数据的。Zope的对象数据库接受彼此有种种关系的许多对象。关系数据存储于非常简单的表格之中,不易映射到对象上去。Zope提供了几种在Zope对象世界中访问关系数据的途径,本章下面将详细介绍“数据库接口”(Database Adapter)和"SQL方法"。
Zope关系数据库支持最普通的应用就是操作网络数据。例如:假定你的人力资源部门管理员工数据,这个数据库提供了一些工具供管理员生成报表、修改数据。然而,对于员工查看、简单管理自己的数据却很困难。如果他的住址变更了,他没有办法更改数据库中自己的地址信息。使用Zope访问关系数据,你的员工无论在家还是在办公室,都可以使用任何浏览器来查看、修改自己的记录。
使用Zope访问关系数据库,你可以利用Zope的所有特性,比如,安全性、动态表达、联网工作等等。使用Zope可以有机地将数据访问、数据表达、数据管理融合在一起。
为了在Zope中使用关系数据,必须建立两种不同的Zope对象:一是“数据库连接对象”,另一个是“Z SQL方法”。前者告诉Zope如何连接一个关系数据库;后者使用前者去连接一个数据库,并描述了对数据库的操作。本章将进一步讨论这两种对象。
10-1 使用数据库连接对象
数据库连接对象用来建立和管理对外部数据库的连接。在创建SQL方法之前必须建立好数据库连接对象,所有的Z SQL方法都必须和一个数据库连接对象关联,Zope提供了下面几个数据库接口(Database Adapter,简称AD):
Oracle
Oracle是一个广泛应用、性能优良的商业关系数据库。它的Zope商业接口由Digital Creations公司编写提供。Oracle可以从其网站试用、购买。
Sybase
这是一个流行的商业关系数据库。它的AD由Digital Creations公司编写提供。Sybase可以从其网站试用、购买。
ODBC
ODBC是一种由众多商业、开源数据库支持的跨平台的数据库工业标准协议。他的DA由DC公司编写提供。
PostgreSQL
这个数据库是开源数据库的领头羊。它有多个DA。像ZPoPy,由Zope社区成员Thierry Michel维护。从PostgreSQL的网站可以得到更多关于PostgreSQL的信息。
MySQL
MySQL是一个发展迅速的开源关系数据库。从其网站可以得到更多的信息。它的DA由Zope社区成员Monty Taylor维护。
Interbase
这是Inprise(Borland)公司的一个开源关系数据库。在该公司的网站上可以得到此数据库更多的信息。你可能对维护此数据库一个分支的社区FireBird感兴趣。Zope社区成员Bob Tierney在维护该数据库的Zope接口。
Gadfly
这个关系数据库是Aaron Waters用Python写的。它内置在Zope中,用来做演示或处理小批量的数据。它很快,但是不适用于处理大量数据,因为它把所有的数据都读入内存来处理。可以在Chordate的网站上获得更多的情况。
除了Gadfly,其它关系数据库都是Zope的外部扩展。就是说,你的数据库可以运行在Zope之外的其他机子上。当然,你的Zope要能访问运行数据库的主机。安装、配置关系数据库超出了本书的范围。上面提到的关系数据库都有自己的安装配置文档。
因为Gadfly在Zope内运行,你不必为它指定具体的连接信息。其它关系数据库则要你具体指定如何连接。这种规格成为“连接字符串”,不同数据库各不相同。例如:图10-2是PostgreSQL的数据库连接表单,可以看到此数据库的连接字符串的格式。
(图10-2 PostgreSQL 数据库连接)
为了从Zope中访问关系数据库,你必须下载安装关系数据库的特定接口。这些接口可以从Zope.org的Products目录下载(Gadflyde的接口已经包含在了Zope中)。本章的示例都是使用Gadfly,但处理过程适用于其它关系数据库。
安装数据库接口之后就可以从添加列表中选择它来创建一个数据库连接对象了。各个数据库连接对象都是相似的。从添加列表选择Z Gadfly Database Connetion会转入Gadfly数据库连接对象的添加表单。
选择Demo数据源,用Gadfly_database_connection作为id,单击添加按钮,生成一个Gadfly数据库连接对象。单击新建的数据库连接对象,看到的是它的状态页。从本页可以知道你是否连接到了数据库(有一个按钮控制数据库的连接和断开)。一般情况下,Zope会自动连接到数据源。某些情况下,你可能想到手工控制数据库的连接。对Gadfly来说,断开连接没有什麽意义,但是对于外部的数据的维护,就有必要手工控制它们的连接。
下一页是属性页,该页显示数据源和连接对象的其他一些属性。当你想把数据源换成另外一个时,就会用到这些属性。图10-3显示了一个属性页的情形。
(图10-3 属性页)
在测试页可以测试你的数据库连接。在该页,你可以直接输入SQL语句操作你的数据库。该页用来测试数据库,同时运行SQL语句(比如创建一个表)。此处不宜输入过多的SQL语句。SQL语句要放在Z SQL方法中,本章后面将作讲解。
让我们在数据库中创建一个表以便展示本章的例子。测试页允许直接输入SQL语句操作数据库。你可以在测试页输入SQL创建数据表而不必使用Z SQL方法。创建名为employees的表使用如下SQL语句:
CREATE TABLE employees
(
emp_id integer,
first varchar,
last varchar,
salary float
)
单击提交按钮运行这条SQL语句。Zope会返回一个验证页告诉你什麽SQL在运行和可能的结果。
此处运行的SQL会因你的数据库不同而各异。数据库建表的确切语句,请参考数据库提供商的文档。
此处的SQL语句将在Gadfly数据库中新建一个名为employees的表。该表有四列:emp_di,first,last, salary。第一列是员工的id,这是标志员工的唯一号码。接着的两列类型是varchar,该类型和字符串类似。salary列的类型是float,即浮点数。不同的数据库提供不同的数据类型,参考相关文档查看你的数据库支持的数据类型。
为了保证员工号是唯一的,需要为表创建一个索引。在测试页输入下面的SQL语句:
CREATE UNIQUE INDEX emp_id ON employees
(
emp_id
)
现在你有了一个表并且创建了一个索引,要验证你的表,转到浏览页。该页显示了数据库的表和它们的模式。在这里,你可以看到一个employees 表。假如点击了前边的“加号”,该表会展开,显示出四列:emp_id,first,last,salary。如图10-4所示:
(图10-4 浏览数据连接)
当创建多表复杂SQL应用时,这是非常有用的——各表的结构一目了然。当然不是所有的数据库都支持表的浏览。
你已经建立了一个数据库连接对象并且新建了一个表,接下来就可以创建Z SQL方法来操作你的数据了。
10-2节 Z SQL方法
Z SQL方法是Zope中通过数据库连接对象执行SQL语句的对象。所有的Z SQL方法都必须关联一个数据库连接对象。Z SQL方法能查询数据,也可以修改数据。一个Z SQL方法可以包含多条SQL语句。
下面创建一个名为hire_employee的Z SQL方法用来给employees表添加一条新纪录。当雇佣新员工时调用这个方法在employees表中插入新员工的记录。从添加列表中选择Z SQL方法转到Z SQL方法的添加表单页,如图10-5所示:
(图10-5 Z SQL方法的添加页)
同新建其它对象一样,你要为Z SQL方法指定id和title。另外,需要为Z SQL方法指定一个数据库连接对象。以hire_employee为id,以上面创建的Gadfly_database_connection为连接对象新建一个Z SQL方法。
接下来你可以给Z SQL方法指定参数。就像脚本一样,Z SQL方法能接受参数。参数用来构造SQL语句。本例中要指定四个参数:员工号,名字,姓氏和员工的薪水。在参数框中输入“emp_id first last salary”,参数可以各自写在一行上,也可以都写在一行中,中间用空格分开。参数也可以像Python一样使用默认参数值。例如:emp_id= 100表示emp_id的缺省值是100。
最后的文本框时查询模板。此处包含的是调用Z SQL时执行的SQL语句。在此处输入下面的SQL语句:
INSERT INTO employees(emp_id,first,last,salary) VALUES
(
<dtml-sqlvar emp_id type="int">,
<dtml-sqlvar first type="string">,
<dtml-sqlvar last type="string">,
<dtml-sqlvar salary type="float">
)
注意到SQL语句也有DTML语句。这里的DTML语句是要把参数值插入到要执行的SQL中。因此,假如emp_id=42,first='Bob',last='Uncle',salary=50000.00,这里的SQL将会是:
INSERT INTO employees (emp_id,first,last,salary) VALUES
(42,'Bob','Uncle',50000.00)
查询模板和SQL相关的DMTL语句将在后面进一步讨论。
新建Z SQL方法有三个按钮供选择。添加按钮将新建Z SQL方法并返回该对象的文件夹。“添加且修改”按钮将新建Z SQL方法并进入此方法的编辑页。“添加并测试”按钮新建Z SQL方法后会自动转到测试页,可以在这里测试新建的Z SQL方法。现在单击添加按钮新建一个Z SQL方法。
现在你创建了插入新员工记录的Z SQL方法,还需要一个Z SQL方法来查询员工。以list_all_employees为id新建一个Z SQL方法。不需要参数,SQL语句如下:
select * from employees
这条简单的SQL语句会返回employees表中的所有记录。现在你有了两个Z SQL方法:一个用来插入新员工,另一个用来显示数据库中所有的员工。让我们测试一下这两个方法:先插入一些新员工,然后罗列出他们。进入 hire_employee方法的测试页,如图10-6所示:
(图10-6 hire_employee的测试页)
在这里可以看到一个有四个输入框的表单,对应Z SQL方法的四个参数。Zope根据Z SQL方法的四个参数自动生成了这个表单。hire_employee方法有四个参数,所以Zope生成了一个四个输入框的表单。输入一个员工号42,名字Bob,姓氏McBob和薪水50000.00测试这个方法。单击测试按钮,你将看到测试的结果。
结果显示本语句不返回任何对象。这是因为hire_employee方法只是插入记录,不进行任何选择,不返回任何记录。同时你可以看到查询模板生成的SQL语句。同期望的一样,sqlvar DTML将四个参数插入了要执行的SQL语句。反复测试这个SQL方法可以将多个员工的信息插入数据库中。
为了验证数据是否出入表中,选择list_all_employees Z SQL方法并进入它的测试页。测试这个方法不需要输入,因为它没有参数。单击提交查询按钮进行测试。
这个方法返回employees表的内容。你加入的新员工都被显示出来了。Zope为你自动生成了这个表格。接下来我们将演示如何创建自己的Z SQL方法的用户界面,并将之应用于你的网站。
10-2-1 调用Z SQL方法
查询关系数据库将返回记录的序列。记录也成为“行”。SQL查询结果总是记录集。甚至只有一条记录,返回的也是一个仅仅包含一条记录的记录集。因此Z SQL方法总是返回记录集(不管记录集是空还是只有一条记录)。
一条记录也称为‘返回对象’。可以把它想成是一条记录映射的Zope对象。该对象的属性对应数据表的模式。
‘记录对象’和其它的Zope对象最重要的一条区别是:‘记录对象’不会是长久的Zope对象。记录对象生命周期很短,一旦它返回了结果就会消亡。下次调用Z SQL方法时将得到新建的记录对象。
DTML可以引用Z SQL返回的记录对象来显示记录。例如:添加一个名为listEmployees的DTML方法,输入下面的内容:
<dtml-var standard_html_header>
<ul>
<dtml-in list_all_employees>
<li><dtml-var emp_id>: <dtml-var last>, <dtml-var first>
makes <dtml-var salary fmt=dollars-and-cents> a year.
</li>
</dtml-in>
</ul>
<dtml-var standard_html_footer>
这个DTML方法调用了Z SQL方法list_all_employees。‘in'语句用来遍历list_all_employees返回的各个记录对象。Z SQL方法总是返回记录对象的列表,所以你总可以从'in'语句中使用记录对象,除非你对它不感兴趣,或SQL方法根本就不返回对象——像 hire_employee一样。
'in'语句定义了list_all_employees返回记录对象的表达模板。假如数据表中有三个员工的信息,listEmployees返回的HTML将像下面所示:
<html>
<body>
<ul>
<li>42: Roberts, Bob
makes $50,000 a year.
</li>
<li>101: leCat, Cheeta
makes $100,000 a year.
</li>
<li>99: Junglewoman, Jane
makes $100,001 a year.
</li>
</ul>
</body>
</html>
'in'语句返回了list_all_employees所有记录对象的HTML表达语句。
下面我们看看如何创建用户界面录入数据并传送这些数据到Z SQL方法中。
10-2-2 给Z SQL方法指派参数
你已经创建了DTML方法listEmployees来调用Z SQL方法list_all_employees显示员工的信息。现在该看看如何给Z SQL方法hire_employee创建用户界面。hire_employee方法要四个参数,它的测试页可以调用这个方法,但是你的web应用程序没办法调用测试页,必须自己创建输入表单供调用。
Z Search Interface可以为你自动生成输入表单。在第九章里你曾用它为一个表单和它的处理生成了搜索表单和返回结果的HTML代码。Z Search Interface也可以为Z SQL方法生成表单/返回的HTML代码。
从添加列表中选择Z Search Interface,指定hire_employee为搜索对象(Searchable Object)。返回页的id(ReportID)用"hireEmployee",表单页用"hireEmployeeForm"作Id。单击添加按钮。
选择新建的hireEmployeeForm并进入其view页,输入一个新员工的employee_id,名字,姓氏,薪水并提交表单。 Zope返回页显示:“这个查询无返回数据”。这是因为Z Search Interface生成的返回页要显示Z SQL方法的返回对象,而hire_employee方法不返回任何对象,它只把员工的信息插入表中。修改DTML方法hireEmployees以便返回更直观的信息。选择hireEmployees方法,下面是它的DTML代码:
<dtml-var standard_html_header>
<dtml-in hire_employee size=50 start=query_start>
<dtml-if sequence-start>
<dtml-if previous-sequence>
<a href="<dtml-var URL><dtml-var sequence-query
>query_start=<dtml-var
previous-sequence-start-number>">
(Previous <dtml-var previous-sequence-size> results)
</a>
</dtml-if previous-sequence>
<table border>
<tr>
</tr>
</dtml-if sequence-start>
<tr>
</tr>
<dtml-if sequence-end>
</table>
<dtml-if next-sequence>
<a href="<dtml-var URL><dtml-var sequence-query
>query_start=<dtml-var
next-sequence-start-number>">
(Next <dtml-var next-sequence-size> results)
</a>
</dtml-if next-sequence>
</dtml-if sequence-end>
<dtml-else>
There was no data matching this <dtml-var title_or_id> query.
</dtml-in>
<dtml-var standard_html_footer>
这段DTML真的很复杂。它用来自动生成序列元素的表格。既然我们不需要这些,可以把他改的简单些:
<dtml-var standard_html_header>
<dtml-call hire_employee>
<h1>Employee <dtml-var first> <dtml-var last> was Hired!</h1>
<p><a href="listEmployees">List Employees</a></p>
<p><a href="hireEmployeeForm">Back to hiring</a></p>
<dtml-var standard_html_footer>
从hireEmployeeForm添加一个新员工,注意hire_employee方法是被DTML的call方法调用的。这是因为我们知道hire_employee方法没有返回对象,不必使用'in'语句,简单实用call方法调用它即可。
现在你已经有了雇佣员工的全部用户界面。使用Zope的安全策略,可以授权给特定的用户来使用这些方法。使用Z Search Interface生成的表单/返回界面也可以方便地修改成你期望的样子。
下一节我们进一步使用SQL进行精确查询。你已经看到Z SQL方法是如何为你创建的基本的SQL查询模板。下一节你可以学到如何创建更多的查询模板。
10-3 动态查询
ZSQL方法查询模板中可以包含DTML语句,这些语句在SQL方法被调用时执行,它们可以动态生成SQL供数据库使用。一些SQL相关的DTML语句用来帮助你构造复杂的SQL查询。下面小节我们将学习sqlvar、sqltest和sqlgroup语句。
10-3-1 用sqlvar插入参数
插入数据时使用正确的数据类型非常重要。如果你使用字符串“12”而要求的数据类型是整型,数据库就会报错。更糟糕的是:不同的数据库支持的数据类型不尽相同。类型验证不只是为了避免出错,而且也是处于安全考虑。假如你有一个查询语句如下所示:
SELECT * FROM employees WHERE emp_id=<dtml-var emp_id>
这个查询是不安全的。因为如果有人输入下面的语句:
"12;DROP TABLE employees"
作为emp_id,执行这条语句表employees将被删除!为了避免这些问题,就必须进行类型检查。sqlvar就是用来做这件事的。上面例子安全的写法应是:
SELECT * FROM employees WHERE emp_id=<dtml-sqlvar emp_id type="int">
sqlvar和DTML的var用法相似。前者会对SQL类型进行检查,当没有输入时默认值设为NULL。sqlvar语句有下面一些属性:
name
这个属性和var语句的name属性一样。它是一个Zope变量或ZSQL方法的参数名字(译注:name是sqlvar语句的名字,即它是一个变量;同时它也是ZSQL方法的参数的名字,即一个输入文本框的名字。前者从后者获取数据)。该变量或参数的值将插入到SQL查询模板中。name属性是必须的,但可以忽略前缀"name="。
type
这个属性决定如何格式化变量或要插入SQL的参数。type的值有string,int,float,nb(非空字符串)。这个属性是必须的。
optional
这个属性用来说明sqlvar可以省略或可以是个空值。假如变量不存在或为空,sqlvar将不会提交这个变量。这个属性是可选的。(译注:可以保持测试输入框为空来理解这个属性。当salvar语句不包含这个属性,空输入会引发异常并报错;当含有这个属性时,返回的SQL语句中sqlvar对应的位置不插入的任何东西——本例会返回SQL语法错误的提示,因为实际返回的是“SELECT * FROM employees WHERE emp_id=”)
type属性是sqlvar语句的关键属性。它负责插入变量类型的正确性。查看附录A获取sqlvar详细的情况。处理SQL时你总是应该使用sqlvar替代var来保证SQL的安全。
10-3-2 使用sqltest进行条件测试
许多SQL都要用到条件比较。它们遍历表中记录,查找符合输入条件的记录。例如:你可能想查询所有薪水超过一定数目的员工。新建一个名为"employee_paid_more_than"的SQL方法,给定一个参数"salary",查询模板中输入:
SELECT * FROM employees WHERE <dtml-sqltest salary op=gt type=float>
单击添加按钮并进入其测试页进行测试。"op"属性是"gt",即“大于”。ZSQL会返回所有薪水高于你输入数字的员工。sqltest语句安全地生成对应表列的SQL条件。在salary文本框中输入10000进行测试。你可以看到提交的是下面的sql语句:
SELECT * FROM employees WHERE salary>10000
sqltest语句根据特定的数据库做类型检查后把生成的条件语句插入SQL。sqltest语句有如下属性:
name
要插入的变量名。(译注:它同时是输入框的名称;如果sqltest语句省略column属性,它同时还是SQL条件中表字段的名字。)
type
同sqlvar语句一样。
column
SQL条件中表字段的名字。注意它和name属性的区别。
multiple
提供多值的标志。这个属性用来测试字段是否在一个变量集中。例如:假如name是字符串的列表"Bob","Billy",<dtml-sqltest name type="string" multiple>返回如下SQL:
name in ("Bob","Billy")
(译注:上面的"name"指的是字段的名字。)
optional
同sqlvar语句。
op
比较操作符。它们可以是eq(等于)、gt(大于)、lt(小于)、ge(大于等于)、le(小于等于)、ne(不等于)。
查看附录A进一步了解sqltest语句。假如你的数据库提供另外的比较操作(比如'like'),你可以在sqltest中使用它们。假如name是字符串"Mc%",SQL语句
<dtml-sqltest name type="string" op="like">
会返回
name like 'Mc%'
sqltest语句帮助你创建正确的sql查询。通常,使用sqltest会比直接使用比较语句更灵活,处理不同类型输入,不同的数据库时会省去很多麻烦。
10-3-3 使用sqlgroup语句创建复杂查询
sqlgroup语句用来创建不定参数的SQL查询。根据参数的多寡,生成的SQL或复杂或简单。
下面是一个无条件SQL查询:
SELECT * FROM employees
下面是几个有条件的SQL查询:
SELECT * FROM employees WHERE (salary>10000.00)
SELECT * FROM employees WHERE(salary>10000.00 and first in ('Jane','Cheetach','Guido'))
SELECT * FROM employees WHERE(first='Old' and last='McDonald')
上面三个查询都可以用一个ZSQL方法来实现。使用不同的参数,这个方法会生成特定的sql语句。下面的sql模板就会生成上面的三个查询:
select * from employees
<dtml-sqlgroup where>
<dtml-sqltest salary op=gt type=float optional>
<dtml-and>
<dtml-sqltest first op=eq type=string multiple optional>
<dtml-and>
<dtml-sqltest last op=eq type=string multiple optional>
</dtml-sqlgroup>
假如sqlgroup语句中有内容,它会提交'WHERE'并且设置查询的条件;如果没有参数,'WHERE'是不会插入sql的。
sqlgroup语句中用到了and语句。它同样会根据情况插入或不插入'and'。'or'语句也一样。
本例也演示了multiple在sqltest语句中的使用。假如first或last的值是一个列表,正确处理组值的sql会产生。
sqlgroup语句可以嵌套使用:
select * from employees
<dtml-sqlgroup where>
<dtml-sqlgroup>
<dtml-sqltest first op=like type=string>
<dtml-and>
<dtml-sqltest last op=like type=string>
</dtml-sqlgroup>
<dtml-or>
<dtml-sqltest salary op=gt type=float>
</dtml-sqlgroup>
给定一些参数,这个SQL模板返回下面的SQL:
select * from employees
where
( (first like 'A%'
and
last like 'Smith'
)
or
salary > 20000.0
)
使用sqlgroup语句可以构造复杂查询。为了简化sql语句,你可以不使用sqlgroup语句。但是,当你创建了若干不同却又有相似的ZSQL方法时,你可以试试使用sqlgroup语句来合并它们。
10-4 高级话题
目前为止,你已经看到了如何连接一个关系数据库,查询数据,操作数据库,创建用户界面。这些都是访问Zope关系数据库的基本知识。
在下面的几节当中你可以看到如何更紧密地结合Zope以及如何增强它们的性能。首先看一下如何直接指定参数和使用‘获取’ (acquisition)技术来给ZSQL方法传递参数。接着可以看到如何直接通过URL‘寻找’(traversal)调用ZSQL方法。之后是如何给记录对象绑定类。最后看到的是使用缓冲增强功效以及Zope的事务处理。
10-4-1 直接指定参数调用ZSQL方法
如果从DTML中调用ZSQL方法不明确指定参数,系统会自动把参数传递给ZSQL方法。到现在为止,我们使用的就是这种方法。通过搜索表单查询数据库时这种方法工作的很好,但是有些时候需要手工或通过脚本来查询数据。从DTML或Python脚本中可以直接指定参数调用ZSQL方法。例如: ZSQL方法employee_by_id如下:
SELECT * FROM employees WHERE
调用它使用下面的DTML方法:
,
's employee id is .
first> makes per year.
employee_by_id方法只返回一条记录,in语句只循环一次。本例像一样调用ZSQL方法,并传递给它一个参数emp_id,就像Python脚本一样:
## Script (Python) "join_name"
##parameters=id
##
for result in context.employee_by_id(emp_id=id):
return result.last + ', ' + result.first
这段脚本通过参数id将参数传递给employee_by_id的emp_id参数,之后它‘遍历’这个单记录的列表,将姓和名用逗号分开后返回。
通过直接传递参数给ZSQL方法可以对关系数据库进行更多的控制。从DTML和Python中直接指定参数调用ZSQL方法就像调用其它Zope方法一样。