级别: 初级 Teodor Zlatanov (tzz@bu.edu), 程序员, Gold Software Systems
2005 年 4 月 04 日 在本期文章中,Ted 介绍的是 Perl 和数据库。具体来说,他通过 Class::DBI CPAN 模块和 MySQL 来向您介绍如何在数据库表中嵌入 Perl。
数据库以及使用数据库的应用程序是当今的计算基础设施(computing infrastructures)所必不可少的。
从纯文本数据库(比如 UNIX® /etc/passwd 文件)到大型数据库(比如追踪购物习惯或者预防信用
卡欺诈的数据库),它们无处不在。
本文从一个特别的角度研究了 Perl 工具与普通 RDBMS(关系数据库管理系统,relational database management systems)
的集成:在数据库表中嵌入 Perl。我已经料到会有数据库设计纯化论者(purist)对此表示反对 ——
这种方法既不标准,也不可移植。我知道这一点,但是,有两件事情,那些纯化论者应该要知道:
- “TMTOWTDI”是 Perl 精髓。它表示“做这件事情不只有一种方法(There's More Than One Way To Do It)”。
- Perl 程序员最典型的特点是懒惰而高效(productive laziness,或者说是 lazy productivity)。
嵌入是一件懒惰而高效的事情,但是,如果您不理解迁移到 Java® 的重要性(举例来说),那么您就
应该避免在数据库中嵌入 Perl 代码。在本文中,我们将使用 Class::DBI CPAN
模块来管理数据库的表(下一节将进行详细介绍)。由于本文既适用于初级程序员,也适用于有经验
的程序员,所以您应该具备一些数据库编程的知识,尤其是使用 MySQL(我的环境)的 Class::DBI CPAN
模块。至少,您应该理解如何安装和使用 MySQL,以及如何在 MySQL 数据库中创建表。(否则,请参阅
参考资料 部分中关于 MySQL 的快速上手教程。)
Class::DBI 的功能
Class::DBI 是一个强大的 Perl 模块,它可以在代码中模拟关系数据库表的设计。
使用它,可以创建一对一(one-to-one)和一对多(one-to-N)的关系,如果多付出一些努力,甚至可以创建多对多(N-to-N)的关系
(Class::DBI 文档中涵盖了所有的内容;查阅 参考资料
中 Class::DBI 主页的链接)。
一旦写好设置代码,Class::DBI 的使用会很简单。设置代码通常由一些行构成,
这些行是对数据库的描述,比如数据库驱动程序、用户名和密码,以及到哪里去找到它。从这时起,插入、删除以及
修改记录等操作都只需要一行就可完成。Class::DBI 将对记录的操作简化为
单行形式(one-liner),这一事实使得在数据库中嵌入 Perl 代码既切实可行又令人满意。
再长一点点都会过于笨拙。
Class::DBI 可以做多得多的事情。一定要阅读它的文档 ——
我保证您会头痛(也会有一些惊喜),因为您会太多次拍击自己的前额,所以请先准备点阿斯匹林。
现在,让我们来建立一些表。
建立表
在深入地研究细节之前,我们应该先回顾体系结构和表,比如 Perl 代码的模板(template)表。例如,对象
的删除会用到“DELETE”模板。下面的清单举例说明了使用 MySQL 定义那个表的方法。
清单 1. code templates 表的定义(MySQL)
DROP TABLE IF EXISTS codetemplates;
CREATE TABLE codetemplates (
name varchar(200) NOT NULL,
template longtext NOT NULL,
PRIMARY KEY (name)
) TYPE=MyISAM;
LOCK TABLES codetemplates WRITE;
INSERT INTO codetemplates VALUES ('DELETE', '$target->delete()');
INSERT INTO codetemplates VALUES ('MODIFY', '$target->VERB(DATUM)');
UNLOCK TABLES;
|
这只是一个由两列构成的基本的表,以模板名作为主键,所以任何两个模板都不能具有相同的名称。
编程序和说话非常类似,因为都需要动词(verb)、名词(noun)、形容词(adjective),等等。实际上,Perl 的创建者就是一位语言学家,
他在语言学方面的经验明显地影响了 Perl 的发展。就嵌入式 Perl 代码而言,必须要有的是“名词”(被影响的内容)和“动词”(采取的动作)。通常不需要“修饰语”(句子中的副词和形容词)。
“名词”是代码的目标。我将其命名为 $target,它是必需的。为动作准备的代码将具备好 $target。
“动词”是代码采取的动作。我将通过对代码求值来执行动作;如果代码可能失败,那么我将会捕获那个错误。
Perl 为此提供了 eval() 函数。字符串 VERB
将作为修饰语动作的占位符。
需要慎重对待“修饰语”。当不需要它时,比如在简单的对象删除情形中,您不必担心。当需要它时,我们将给出一个
占位符字符串。这个字符串是 DATUM,在关于
嵌入式修改 一节中您将了解到如何使用它。
嵌入式删除
嵌入式删除确实非常简单。嵌入的命令只是 $target->delete();
Class::DBI 模块将完成其余所有事情。这通常会出色地完成,
但是如果您定义了 has_many() 关系,则有可能会触发级联(cascading)删除。
级联删除起初是一个可怕的概念。Class::DBI 模块必须要知道(通过
has_many() 关系)哪些对象依赖于正在被删除的对象。例如,
一辆汽车拥有多个(has_many())轮胎。
通常,删除依赖的对象是有意义的,但也经常不是那样。有一个常见的例子是部门与雇员的关系,雇员的存在并不依赖于部门,而只是关联到部门(因为一个雇员可以在多个部门之中)。
Class::DBI 的作者已经承诺要为此提出一个标准的解决方案,让程序员可以指定某个
has_many() 关系不应触发级联删除。当前推荐的解决方案如清单 2 所示。
清单 2. 动态禁用级联删除
My::Department->add_trigger(
before_delete => sub
{
$_->department(undef) foreach shift->employees;
});
|
它在删除部门之前将每一个雇员从部门中分离出来。
查阅 Class::DBI 站点,了解是否已经发布了标准的解决方案。
完成此功能的不正式(UNDOCUMENTED)而且也不可靠的(UNRELIABLE)最新方法是,为
has_many() 设置调用使用 no_cascade_delete => 1
选项。这当然比定制触发器更具吸引力,但如果 Class::DBI 不再支持它,
则这将不再有效。请自己多加小心。
如果您已经完全了解了级联删除,并保证会多加小心,那么您就可以来看执行嵌入的
$target->delete() 代码的实际代码。
这里假定您已经建立了清单 1 中的“codetemplates”表,同时正常使用
Class::DBI 模块,并且已经编写了使用
那个表的 CodeTemplate.pm 模块。
清单 3. 执行嵌入的代码
my $codetemplate = CodeTemplate->retrieve('DELETE');
foreach my $target (Class1->retrieve(500), Class2->retrieve(600))
{
next unless defined $target;
if ($codetemplate->template())
{
my $result = eval $codetemplate->template();
if ($@)
{
print "The evaluation failed\n";
}
elsif (defined $result)
{
print "This operation resulted in [$result]\n";
}
else
{
print "This operation's result was undefined\n";
}
}
}
|
Class1 和 Class2 是假想出来的;您也可以使用 retrieve_all() 函数
来获得 Class1 和 Class2 所描述的每张表的所有行。
如您所见,大部分工作是处理错误,因为在这样简单的代码中它也会出现。注意,如果您愿意,
可以保存操作的结果。对于删除来说这并不重要,但对其他操作来说可能是重要的。
嵌入式修改
修改更为有趣。目光敏锐的读者会注意到清单 1 中展示了修改模板的代码:
$target->VERB(DATUM)。除了数据比较奇怪之外,
这个模板说明了什么?
回忆前面我所定义的关于作为名词、动词和可选修饰语的操作的必需信息。
如果操作是“MODIFY”,那么只是在获取数据的时候修饰符才是可选的。
让我们来完成一个示例。对象(目标名词或者主题)是 $target,属于类
Employee。那个类来自于数据库中某处的“employees”表,具有
“name”字段,因此设置其名字的原始代码可以如下所示。
清单 4. 仅设置雇员名字
my $target = Employee->retrieve(500);
$target->name("Jonesy");
|
当然,如果做的事情如此简单,那么我就配不上我的专家称号,而且也不会再做程序员。
我必须加深这个示例的难度。
清单 5. 再次感受
my $target = Employee->retrieve(500);
my $data = "Jonesy";
my $verb = "name";
my $codetemplate = CodeTemplate->retrieve('MODIFY');
my $template = $codetemplate->template();
if ($template)
{
$template =~ s/VERB/$verb/g;
# this is how you can get the OLD value of the field
$retriever = $template;
$retriever =~ s/DATUM//g;
$old_value = eval $retriever;
$template =~ s/DATUM/\$data/g;
}
my $result = eval $template;
if ($@)
{
print "The evaluation failed\n";
}
elsif (defined $result)
{
print "This operation resulted in [$result]\n";
}
else
{
print "This operation's result was undefined\n";
}
|
并不是要开玩笑,出于某种原因,这个示例较为复杂。现在,通过设置一个修饰语和一个动词,您就可以
设置任何字段,而同时保存其先前的值。
您是否注意到某些内容比较有趣?这里的代码看起来与清单 3(它处理的是删除)非常类似。
您可以轻松地将清单 3 和清单 5 组合起来,获得一个通用代码模板处理器。
可以随意替换删除模板,但是您确实必须当心字段的旧值的获得,因为删除只是会删除
那个对象。所以只是在“MODIFY”模板中才去获得旧值。
当然,您可以添加其他模板,如 ADD 和 SEARCH。它们以同样的方式工作:为模板处理程序给出一个
模板、一个动词和一个修饰语,它将恰如其分地运行结果代码。
与其他语言和可选方法的兼容性
如果阅读本文时您会想到“嗨,为什么只是 Perl?”,其实您不是惟一这样认为的人。撰写本文我曾
考虑过 Perl 和 Java™ 或其他混合语言环境,但 Perl 尤其适合于单行
模板。我认为以多种语言编写的更长的模板,将使系统不能得到及时维护,所以,我建议您,只要
可能,请坚持只使用一种语言 —— 并保持语言的简洁。Python 或许可以取代 Perl(因为它拥有相当
短小的数据库处理指令),但是我尚未完成 Python 的测试实现。
阅读本文时,每个人迟早都会有的另一个想法是“为什么不把 opcode-to-template 转换表包含在代码中?”
您可以那样做。我不建议那样去做的原因是,您可以分两次有效地转换代码,一次是从操作代码(opcode)到模板,
再一次是从模板到实际的代码。为实现示例,您同样可以将操作代码“DELETE”映射到您的环境和语言
中任何适当的用于删除的函数。不管如何去实现,抽象操作以简化数据管理的总体想法是正确的。
由模板和原始代码可知,抽象的关键应该是简化“名词-动词”或者“名词-动词-修饰语”形式的操作。在
进行此类抽象的过程中,您将获得关于您的软件尚需要和缺少哪些东西的颇有价值的经验。
您将会理解软件从一个状态转到另一个状态的方式,而且(非常重要),如果您可以定义某个操作,
那么同样可以追踪并撤销它。
结束语
我希望您乐于学习在数据库中存储代码模板。
您可能会对 Template Toolkit 感兴趣,它可以让您的模板比前面所示的简单替换更为强大。
不过,要谨防让所有代码成为一系列模板。那样的模板将会过于复杂,而且将变得难以非常迅速地更新。
请一定要权衡能力与简洁,并在可能时二者兼顾。这是优秀设计者的标志。
在使用 Template Toolkit 的情况下,有一个有效的经验方法,即在每个模板中拥有不超过一个循环和
不超过三个内插值替换变量(假定要实现单行方式的模板)。
为操作考虑了代码模板后,您应该去了解一下在 Java Swing 工具包(作为示例)中所实现的通用 MVC(
模型-视图-控制程序,model-view-controller)模式。分离模型、视图和控制程序有很多好处;
当您如本文所示那样去抽象操作时,您可以轻松地进行下一个步骤,即分离数据的视图和模型。
即使您不进行视图-模型的分离,在很多情况下 MVC 模型也会有所价值,所以您应该去了解它
如何工作,以及它可以为您做些什么。
祝您体会到数据库操作的乐趣,并且记住,TMTOWTDI!
参考资料
关于作者  | 
|  |
Teodor Zlatanov 于 1999 年毕业于波士顿大学计算机工程专业,获硕士学位。
自从 1992 年以来,他一直从事编程工作,使用的语言包括 Perl、Java、C 和 C++。
他的兴趣在于开放源代码工作、Perl、文本解析、3 层客户机—服务器数据库体系结构、UNIX 系统管理。
欢迎提出建议或者纠正错误,您可以通过
tzz@bu.edu 与 Ted 联系。 |
对本文的评价
|