Mysql入门系列:Perl DBI基础(1)
2006-12-31 10:50:01 来源:WEB开发网本节提供DBI 的背景信息—在编写自己的脚本和支持其他人编写的脚本时,需要这些信息。如果已经熟悉DBI,则可以略过这节,直接跳到7 . 3节“运行DBI”。
DBI 数据类型
从某些方面来说,使用Perl DBI API 类似于使用第6章介绍的C 客户机库。在使用C 客户机库时,主要依靠指向结构或数组的指针来调用函数和访问与MySQL相关的数据。在使用DBI API 时,除了函数称为方法,指针称为引用外,也调用函数和使用指向结构的指针。
指针变量称为句柄,句柄指向的结构称为对象。
DBI 使用若干种句柄。它们往往通过表7-1所示的惯用名称在DBI 文件中引用。而惯用的非句柄变量的名称如表7 - 2所示。实际上,在本章中,我们并不使用每个变量名,但是,在阅读其他人编写的DBI 脚本时,了解它们是有用的。
表7-1惯用的Perl DBI 句柄变量名
名称 | 说明 |
$dbh | 数据库对象的句柄 |
$sth | 语句(查询)对象的句柄 |
$fh | 打开文件的句柄 |
$h | “通用”句柄;其意义取决于上下文 |
表7-2 惯用的Perl DBI 非句柄变量的名称
名称 | 说明 |
$rc | 从返回真或假的操作中返回的代码 |
$rv | 从返回整数的操作中返回的值 |
$rows | 从返回行数的操作中返回的值 |
@ary | 查询返回的表示一行值的数组(列表) |
一个简单的DBI 脚本
让我们从一个简单脚本d um p _ member s开始,它举例说明了DBI 程序设计中若干标准概念,如与MySQL服务器的连接和断开、检索数据等。此脚本产生的结果为以制表符分隔形式列出的历史同盟成员。这个格式本身并不让人感兴趣:在这里,了解如何使用DBI 比产生漂亮的输出更为重要。
dump_members 如下:
要想自己试验这个脚本,可以下载它(请参阅符录A),或使用文本编辑器创建它,然后使之可执行,以便能运行。当然,可能至少需要更改一些连接参数(主机名、数据库名、用户名和口令)。本章中的其他DBI 脚本也是这样。在参数缺省时,本章下载脚本的权限设置为只允许读。如果您将自己的MySQL用户名和口令放在它们之中,我建议将它们保留为这种方式,以便其他人不能读取这些值。以后,在7 . 2 . 8节“指定连接参数”中,我们将看到如何从选项文件中获得这些参数,而不是将它们直接放在脚本中。
现在,让我们逐行看完这个脚本。第一行是标准行,指出哪里可以找到Perl 的指示器:
#! /usr/bin/perl
在本章将要讨论的脚本中,每个脚本都包含这行;以后不再说明。此脚本中至少应该含有一个简短的目的说明,这是一个好主意,所以下一行是一个注释,给阅读此脚本的人提供一个关于它做什么的线索:
# dump_members.dump Historical League's membership list
从‘#’字符到行尾部的文本为注释。有必要做一些练习,就是在整个脚本中编写一些注释来解释它们如何工作。
接下来是两个use 行:
use DBI;
use strict;
use DBI 告知Perl 解释程序它需要引入DBI 模块。如果没有这一行,试图在脚本中做与DBI 相关的任何事,都将出现错误。不需要指出想要哪个DBD 级别的模块。在连接数据库时,DBI 会激活相应的模块。
use strict 告知Perl,在使用它们之前需要声明变量。如果没有use strict 行,也可以编写脚本,但是,它有助于发现错误,所以建议始终要包括这行。例如,置为严格模式时,如果声明变量$ my _ v a r,但是之后错误地用$mv_var 来访问,则在运行这个脚本时,将获得下面的消息:
Global symbol "$mv_var" requires explicit package name at line n
这个消息会使您想,“怎么了?$ m v _ v a r?我从未使用过这种名称的变量!”,然后,找到脚本中的第n行,看是什么问题,并改正它。如果不用严格模式, Perl 不会给出$ m v _ v a r;将只是简单地按具有un d e f(未定义的)值的该名称创建一个新的变量,并毫无动静地使用它,然后,您会莫名其妙脚本为什么不工作。
因为我们在严格模式下操作,所以我们将定义脚本使用的变量:
现在我们准备连接数据库:
connect( ) 调用作为DBI->connect( ) 来调用,因为它是DBI 类的方法。不必真正知道它是什么意思;它只是一个使人头痛的面向对象的行话(如果的确想知道,那么它意味着connect( ) 是“属于”DBI 的一个函数)。connect( ) 有若干参数:
数据源。(经常调用的数据源名称,或D S N。)数据源格式由要使用的特定DBD 模块需求来确定。对于MySQL驱动程序,允许的格式如下:
"DBI:mysql:db_name"
"DBI:mysql:db_name:host_name"
对于第一种格式,主机名缺省为localhost(实际上有其他允许的数据源格式,我们将在后面7 . 2 . 8节“指定连接参数”中讨论)。“DBI”大写没关系,但是“ mysql”必须小写。
用户名和口令。
表示额外连接属性的可选参数。这个参数控制DBI 的错误处理行为,我们指定的看起来有点奇怪的构造启用了RaiseError 属性。这导致DBI 检查与数据库相关的错误,并显示消息,而且只要它检测到错误就退出(这就是为什么在dump_members 脚本中的任何地方都没有看到错误检查代码的原因; DBI 将它全部处理了)。7 . 2 . 3节“处理错误”包括了对错误响应的可选方法。
如果connect( ) 调用成功,则它返回数据库句柄,我们分配给$dbh(如果connect( ) 失败,通常返回un d e f。然而,因为我们在脚本中启用了R a i s e E r r o r,所以connect( )不返回;但是,DBI 将显示一条错误消息,并且在出现错误时退出)。
连接到数据库后, dump_members 发布一条SELECT 语句查询来检索全体成员列表,然后,执行一个循环来处理返回的每一行。这些行构成了结果集。
为了完成S E L E C T语句,首先需要准备,然后再运行它:
# issue query
$sth=$dbh->prepare("SELECT last_name,first_name,suffix,email,"
"street,city,state,zip,phone FROM member ORDER BY last_name");
$sth->execute();
利用数据库句柄调用prepare( );在执行前,它将SQL 语句传递给预处理的驱动程序。实际上,在这里某些驱动程序做了一些有关这条语句的事情。其他驱动程序只是记住它,直到调用execute( ) 使这条语句被执行为止。从prepare( ) 返回的值是一个语句句柄$ s t h,如果出现错误,则为un d e f。在进一步处理与这条语句相关的所有内容时,都使用这个语句句柄。
请注意,指定的这个查询没有分号结束符。您无疑有这样的(经过长时间使用mysql程序养成的)习惯,用‘ ;’字符终止SQL 语句。然而,在使用DBI时,最好打破这个习惯,因为分号经常导致查询出现语法错误而失败。向查询增加‘ g’也类似,使用DBI 时不要这样。
在调用一个方法而不用向它传递任何参数时,可以没有这个圆括号。下列两个调用是等价的:
$sth->execute();
$sth->execute;
我宁愿有圆括号,因为它使人感到这个调用看上去不像变量。您的选择就可能不同了。
调用execute( ) 后,可以处理成员列表的行。在dump_members 脚本中,提取行的循环简单地显示了每行的内容:
fetchrow_array( ) 返回含有当前行的列值的数组,在没有剩余的行时,返回一个空数组。这样,此循环提取了由SELECT 语句返回的连续行,并显示列值之间用制表符分隔的每一行。在数据库中NULL 作为undef 值返回到Perl 脚本,但是将它们显示为空字符串,而不是单词“NULL”。
请注意,制表符和换行符(表示为‘ t’和‘ n’)括在双引号中。在Perl 中,只解释出现在双引号内的转义符序列,不解释出现在单引号内的转义符序列。如果使用单引号,则输出将为字符串“ t”和“ n”。
提取行的循环终止以后,调用finish( ) 告知DBI 不再需要语句句柄,并且释放分配给它的所有临时资源。实际上,除非只提取结果集的一部分(无论是设计的原因,还是因为出现一些问题),否则不需要调用finish( )。然而,在提取循环之后, finish( ) 始终是很保险的,我认为调用并执行finish( ),比区分何时需要,何时不需要更容易一些。
我们已经显示完了全部成员列表,所以我们可以从服务器上断开连接,并且退出:
$dbh->disconnect();
exit(0);
dump_members 示出了许多DBI 程序的大多数通用概念,而且不必了解更多的知识,就可以着手编写自己的DBI 程序。例如,要想写出一些其他表的内容,所需要做的只是更改传递给prepare( ) 方法的SELECT 语句的文本。而且实际上,如果想了解这种技术的某些应用,可略过这部分,直接跳到7 . 3节“运行DBI”中讨论如何生成历史同盟一年一度的宴会成员列表程序和League 打印目录的部分。然而,DBI 提供许多其他有用的功能。下一节介绍了一些,以便能够在Perl 脚本中看看如何完成比运行一条简单的SELECT 语句更多的事情。
处理错误
在dump_members 调用connect( )方法时,应该启用RaiseError 错误处理属性,以便这些错误用一条错误消息就能自动地终止相应的脚本。也可以用其他方式处理这些错误。例如,可以自己检查错误而不必使用DBI。
为了查看如何控制DBI 的错误处理行为,我们来仔细查看一下connect( ) 调用的最终参数。下面两个相关的属性是RaiseError 和P r i n t E r r o r:
如果启用R a i s e E r r o r(设为非零值),如果在DBI 方法中出现错误,则DBI 调用die( ) 来显示一条消息并且退出。
如果启用P r i n t E r r o r,在出现DBI错误时,DBI 会调用warn( ) 来显示一条消息,但是相应脚本会继续执行。
缺省时, RaiseError 是禁用的,而PrintError 启用。在此情况下,如果connect( )调用失败,则DBI 显示一条消息,而且继续执行。这样,如果省略connect( ) 的四个参数,则得到缺省的错误处理行为,可以如下检查错误:
$dbh=DBI->connect($dsn,$user_name,$password) or exit (1);
如果出现错误,则connect( ) 返回undef 表示失败,并且触发对exit( ) 的调用。因为DBI 已经显示了错误消息,所以您就不一定要显示它了。
如果明确给出该错误检查属性的缺省值,可如下调用connect( )。
$dbh=DBI->connect($dsn,$user_name,$password,{RaiseError=>0,PrintError=>1})
or exit (1);
这就需要更多的编写工作,但是即使对不经意的读者,处理错误行为也会更为明显。
如果想自己检查错误,并显示自己的消息,应该禁用RaiseError 和P r i n t E r r o r:
变量$DBI::err 和$ DBI : :er r s t r,只用于所显示的die( ) 调用中,有助于构造错误消息。它们含有MySQL错误代码和错误字符串,非常像C API 函数中的mysql_errno( ) 和mysql_error( )。
如果仅仅要DBI 处理错误,以便不必自己检查它们,则启用R a i s e E r r o r:
$dbh=DBI->connect ($dsn,$user_name,$password,{RaiseError=>1});
到目前为止,这是最容易的方法,并且是dump_members 带来的。如果在脚本退出时,想要执行某种类型的清除代码,启用RaiseError 可能是不恰当的,尽管在这种情况下,可以重新定义$SIG{_DIE_} 句柄,可以做想做的事情。
避免启用RaiseError 属性的另一个原因是DBI 在它的消息中显示技术信息,如下:
disconnect(DBI::db=HASH(0x197aae4)invalidates 1active statement.Either
destroy statement handles or call finish on them before disconnecting.
对于编程者来说,这是好的信息,但对普通用户可能没有什么意义。在此情形,最好自己检查错误,以便可以显示对期望使用这个脚本的人更有意义的消息。或者也可在这里考虑重新定义$SIG{_DIE_} 句柄。这样可能很有用,因为它允许启用RaiseError 来使错误处理简单化,而不是用自己的消息替换DBI 给出的缺省错误消息。为了提供自己的_DIE_ 句柄,可在执行任何DBI 调用以前,进行下面的工作:
$SIG{_DIE_}=sub{die "Sorry,an error occurred ";};
也可以用普通的风格定义一个子例程,并利用这个子例程的引用来设置这个句柄值:
除了在connect( ) 调用中逐字传递错误处理属性之外,还可以利用散列定义它们,并传递对这个散列的引用。有人发现以这种方式准备属性设置使脚本更容易阅读和编辑,但是在功能上这两种方法是相同的。下面是一个说明如何使用属性散列的样例:
下面的脚本dump_members2 举例说明了当要自己检查错误并显示自己的消息时,如何编写脚本。dump_member2 处理和dump_members 一样的查询,但是明确地禁用PrintError 和R a i s e E r r o r,然后测试每个DBI 调用的结果。如果出现错误,在退出以前,脚本调用了子例程bail_out( ) 显示消息及$DBI::err 和$DBI::errstr 的内容:
除了bail_out( ) 是退出而不是返回到调用者以外, bail_out( ) 类似于我们在第6章中为编写C 程序使用的print_error( ) 函数。每次想显示错误消息时, bail_out( ) 解除了写出$DBI::err 和$DBI::errstr 名称的麻烦。同样,通过封装显示到子例程的错误消息,可更改子例程使整个脚本中错误消息的格式一致。
dump_member2 脚本在提取行循环的后面有一个测试,这是dump_members 所没有的。因为如果在fetchrow_array( ) 中出现错误,dump_members2 不会自动地退出,所以人们判断循环是因为结果集读取完成而终止(正常终止),还是因为出现错误而终止做出确定是很困难的。当然,任何一种方式,循环都将终止,但是如果出现错误,则将删截脚本的输出。如果没有错误检查,运行该脚本的人将无法知道是否有错!如果自己检查错误,应该检查提取循环的结果。
处理不返回结果集的查询
D E L E T E、INSERT、REPLACE和UPDATE等执行后不返回行的语句比S E L E C T、DESCRIB、EXPLAIN 和SHOW 等执行后返回行的语句的处理相对要容易一些。为处理一条非SELECT 语句,利用数据库句柄,将它传递给do( )。do( ) 方法在一个步骤内准备和执行该查询。例如,开始输入一个新的成员, Marcis Brown,终止日期为2002 年6 月3 日,可以这样做:
do( ) 方法返回涉及行的计数,如果出现错误,则返回un d e f。因为各种原因,可能出现错误(例如,这个查询可能是畸形的,或可能没有访问这个表的权力)。对于非undef 的返回,注意那些没有受到影响的行的情况。当这种情况发生时, do( ) 不返回数字0;而是返回字符串“0 E 0”(0的Perl科学计数法形式)。“0 E 0”在数值上等价于0,但是,在条件测试中将其视为真,以便可以将其与早期的undef 区别。如果do( ) 返回0,则区分是出现了错误( un d e f)还是“没有受到影响的行”这两种情况将更困难。使用下面的两个测试之一可以检查错误:
if (!defined ($rows)){#error}
if (!$rows) {#error}
在数值环境中,“0 E 0”与0 等价。下面的代码将正确地显示$rows 的任何非undef 值的行数:
也可以用printf( ) 使用‘% d’格式显示$row 来强制进行隐含的数字转换:
do( ) 方法等价于后跟execute( ) 的prepare( )。前面的INSERT 语句可以不调用do( ),如下发布:
处理返回结果集的查询
本章提供了有关实现SELECT 查询中提取行循环的若干选项的详细信息(或其他类似于SELECT 的返回行的查询,如DESCRIB E、EXPLAIN 和S H O W )。还讨论了如何获得结果中行数的计数值,如何处理不需要循环的结果集,以及如何一次检索整个结果集的全部内容等。
1. 编写提取行的循环
dump_members 脚本利用DBI 方法的标准序列检索数据:prepare( ) 使驱动程序处理查询,execute( ) 开始执行这个查询, fetchrow_array( ) 提取结果集中的每一行, finish( ) 释放与这个查询相关的资源。
prepare( )、execute( ) 和finish( ) 是处理返回行的查询中非常标准的部分。然而,对于提取的行,fetchrow_array( ) 实际上只是若干方法中的一种(请参阅表7 - 3)。
方法名 | 返回值 |
fetchrow_array( ) | 行值的数组 |
fetchrow_arrayref( ) | 对行值数组的引用 |
fetch( ) | 与fetchrow_arrayref( ) 相同 |
fetchrow_hashref( ) | 对行值的散列引用,列名键索引 |
下面的例子说示出了怎样使用每个提取行方法。这些例子在整个结果集的行中循环,对于每一行,显示由逗号分隔的列值。在某些情况下,编写这些显示代码还有一些更有效的方法,但是这些例子是以能够说明访问单个列值的语法的方式编写的。
可如下使用fetchrow_array( ):
对fetchrow_array( ) 的每个调用都返回行值数组,不再有行时,返回一个空数组。
选择将返回值分配给数组变量,可以在一组标量变量中提取列值。如果想使用比$ a r y [ 0 ]、$ary[1] 等更有意义的变量名,就可以这样做。假设要在变量中检索名称和电子邮件值,可使用fetchrow_array( ),可以如下选择并提取行:
当然,在以这种方式使用一列变量时,必须保证查询按正确的次序选择列。DBI 不关心SELECT 语句指定列的次序,所以正确地分配变量是您的职责。在提取行时,使用一种称为参数约束的技术,也可以使列值自动分配给单独的变量。
fetchrow_arrayref( ) 类似于fetchrow_array( ),但不返回包含当前行的列值的数组,而是返回这个数组的引用,在没有乘余行时,返回un d e f。如下使用:
通过数组引用$ary_ref 访问数组元素。这类似于引用指针,所以使用了$ary_ref->[$i] 而不是$ a r y [ $ i ]。要想引用整个数组,就要使用@{$ary_ref} 结构。
fetchow_arrayef( ) 不适合在列表中提取变量。例如,下面的循环不起作用:
实际上,只要fetchrow_arrayref( ) 提取一行,这个循环就能正确地运行。但是在没有更多的行时, fetchrow_arrayref( ) 返回un d e f,并且@{undef} 不合法(它有些像在C 程序中试图废弃一个NULL 指针)。
提取行的第三个方法fetchrow_hashref( ),如下使用:
对fetchrow_hashref( ) 的每个调用都返回一个按列名索引的行值散列的引用,在没有更多的行时,返回un d e f。在此情况下,列值不按特定的次序出现; Perl 散列的成员是无序的。然而,散列元素是按列名索引的,所以$hashref 提供了一个单独的变量,可通过它按名称访问任何列值。这使得能按任意需要的次序来提取值(或者它们中的任何子集),而且不必知道SELECT 查询检索的列的次序。例如,如果想访问名称和电子邮件域,可以如下进行:
如果希望将一行值传递给某个函数而又不需要这个函数知道SELECT 语句中指定列的次序时,fetchrow_hashref( ) 是非常有用的。既然如此,可以调用fetchrow_hashref( ) 来检索行,并且编写一个使用列名访问来自行散列值的函数。
更多精彩
赞助商链接