连接关系数据库到Web

作者:Will Benton

为什么有人会想要将数据库连接到网络?一个更好的问题可能是,“为什么想将数据库连接到网络?” 静态 HTML 页面适用于许多事物:文档、超文本书籍、个人页面和其他不变的信息。但是,静态页面存在一些问题

  • 静态页面可能难以维护。如果有人正在管理一个拥有数千页的大型站点,那么仅更改站点的“外观和感觉”将需要大量的工作或与 CGI 和 Perl 共度漫长的夜晚。当站点的内容发生变化时,情况会变得更糟。

  • 静态页面不允许用户输入、反馈或协作。假设您想向您的静态网站添加留言板。您可以设置一个表单,将用户评论邮寄给网站管理员,由网站管理员手动将其放在页面上——您甚至可以设置一个脚本来执行此操作。但是,正如我们稍后将讨论的那样,这会带来一些问题。

  • 静态页面不允许您运营 Web 服务。可以使用静态 HTML 运营网站,但是当今一些最有用的网站,例如 Slashdot (http://slashdot.org/)、CNN (http://www.cnn.com/) 和 Amazon.com (http://www.amazon.com/),都是提供动态数据库支持内容的服务。

数据库管理系统提供对结构化数据的统一访问,就像面向对象编程语言提供一种统一的方式来存储数据和定义对该数据进行操作的方法。如果您正在用 C++ 编写程序并且需要处理字符串,您是希望将所有相关的例程包装在一个类中,还是每次操作字符串时都处理您的字符串实际上是一个 char * 的事实?使用数据库,可以在数据的实际存储方式和使用方式之间提供一个抽象层,这对于几个原因非常有用。您可以使用 C 或 Perl 脚本实现相同的结果,但这不会那么漂亮,并且可能会变得非常糟糕。

我希望这个快速概述足以让您相信您真的想要为您的动态网站使用数据库。在本文中,我将介绍安装 PHP3 (https://php.ac.cn/) 和 PostgreSQL (https://postgresql.ac.cn/) 的说明、一点理论、使用 SQL 和 PHP3 的一些说明以及一个示例应用程序。

安装工具

此描述假定您正在运行 Red Hat,但这些说明中的大多数将适用于其他发行版;无论如何,从源代码安装这些工具都相当容易。

以下是您需要运行示例应用程序并开发自己的应用程序的列表

  • Apache 1.3 或更高版本。您至少需要 1.3 版本,因为 1.2 不支持模块,并且 PHP 作为模块更快且更安全。在 Red Hat 系统上,您需要 apacheapache-devel 软件包。(确保您已安装文件 apxs,因为我们将重新编译 PHP3 Apache 模块。)

  • PHP3 的源 RPM,版本 3.0 或更高版本。

  • PostgreSQL 6.4 或更高版本;示例代码在 6.3 版本上无法运行,需要稍作编辑,因为 6.4 具有 SQL 解析器。

要为 PostgreSQL 支持重建 PHP3,请执行以下步骤

  1. 使用 su 成为 root 用户。

  2. 安装 PHP3 的源 RPM (Red Hat 6.0 上为 rpm -ihv mod_php3-3.0.5-2.src.rpm)。这将在目录 /usr/src/redhat/SPECS 中放置一个“spec 文件”,并在 /usr/src/redhat/SOURCES 中放置一个源代码 tar 文件。由于 Red Hat 附带的 PHP 模块默认未启用数据库支持,因此我们将不得不重新编译它。RPM 使此过程相当轻松。

  3. 由于 PHP3 安装过程假定默认 PostgreSQL 安装,而不是 Red Hat 安装,因此我们需要创建一些符号链接。创建一个目录 /usr/local/pgsql 并创建从 /usr/include/pgsql 到 /usr/local/pgsql/include 以及从 /usr/lib/ 到 /usr/local/pgsql/lib 的符号链接。

  4. 在 spec 文件 (mod_php3.spec) 上调用您喜欢的编辑器并搜索 ./configure;然后添加配置选项 --with-pgsql

  5. 现在使用 rpm 构建二进制包:/rpm -bb mod_php3.spec/

  6. 如果一切顺利,您将在 /usr/src/redhat/RPMS/arch 中获得一个二进制包,其中 /arch 是您的体系结构。安装它,您就可以继续下一步了。

设置 PostgreSQL

PostgreSQL 使用与系统其余部分不同的访问系统;奇怪的是,甚至 root 用户默认也无权访问数据库。数据库系统有自己的用户系统和密码,postgres 默认是数据库管理员帐户。单独访问系统的优点是可以创建没有 UNIX 帐户的数据库用户;这样,您的 Web 应用程序的数据库可以指定访问控制,而不会为您的系统创建潜在的安全漏洞。要将您的 Web 管理员 (web) 添加为数据库用户,请使用 createuser(以 root 用户身份)

# su postgres -c createuser
Enter name of user to add ---> web
Enter user's postgres ID, or RETURN to use UNIX user id: 542 -> 542
Is user "web" allowed to create databases (y/n) y
Is user "web" allowed to add users? (y/n) y
createuser: web was successfully added

然后,作为 web 用户(或您使用的任何帐户),您将能够使用 createdb foo 创建数据库,然后使用 psql foo 尝试对 foo 进行一些查询。

您还需要设置 PostgreSQL 以接受传入的 TCP/IP 连接,以便您的 PHP3 页面可以访问它。幸运的是,System V init 使这变得容易。只需打开文件 /etc/rc.d/init.d/postgresql 并更改行

su postgres -l -c \
 'usr/bin/postmaster -S -D/var/lib/pgsql'

使其读取为

su postgres -l -c \
 '/usr/bin/postmaster -S -D/var/lib/pgsql -i'
同时,您可能还希望出于安全原因指定与默认端口 (5432) 不同的端口。要在不同的端口上运行 PostgreSQL 后端,只需在上面的行中附加 -p port 即可。
您需要了解的所有 SQL 知识(并非真的如此)

世界上几乎每个关系数据库都使用 SQL(或 SQL 的某些扩展版本)作为其查询语言。SQL 允许您定义表、根据给定条件选择记录、更新一个或多个记录中的值以及删除记录。这只是 SQL 的简要介绍;有关更完整的参考,请参阅资源。

创建表

要创建表,请使用 CREATE TABLE 语句。其语法如下

CREATE TABLE tablename (field-1 type-1, ..., field-n type-n)

在 psql 中,您需要在每个语句末尾添加分号。这些分号不是 SQL 语言的一部分,而是为了 psql 的词法分析器的方便。

您还可以将字段声明为 NOT NULLUNIQUEPRIMARY KEY,或将值指定为字段的 DEFAULT。PostgreSQL 将在主键字段上创建索引。不幸的是,截至 6.4 版本,PostgreSQL 不支持外键,但至少解析器不会在 SQL REFERENCES 关键字上报错。

这是一个示例,类似于 UNIX 密码文件

CREATE TABLE passwd
   (username    varchar(8) PRIMARY KEY,
   -- PRIMARY KEY implies UNIQUE
    cryptedpass char(13),
    uid         int UNIQUE NOT NULL,
    gid         int NOT NULL,
    gecos       varchar(80),
    -- the GECOS field (real name, office, etc.)
    homedir     varchar(80),
    shell       varchar(50) DEFAULT '/bin/sh');

请注意,SQL 使用双破折号开始注释,注释以换行符结尾。

插入数据

要将数据插入到表中,请使用 INSERT 语句

INSERT INTO tablename (field-1, ..., field-n)
VALUES (value-1, ..., value-n/)

如果您要将值插入到每个字段中,则无需指定字段名称。以下是我们刚刚创建的表的示例

INSERT INTO passwd (username, cryptedpass,
   uid, gid, gecos, homedir, shell)
VALUES ('fred', '37MniLTaiPLaL', 42, 500,
   'Fred Mbogo', '/home/fred/', '/bin/sh');
请注意,SQL 使用单引号表示字符串常量。任何隐藏的 Pascal 程序员都会感到宾至如归。
检索数据

SQL SELECT 语句返回值满足特定条件的记录。以下是一些 SELECT 在操作中的示例

SELECT * FROM passwd;
-- returns all fields of all records
SELECT username FROM passwd;
-- returns all usernames
SELECT * FROM passwd WHERE username = 'fred';
SELECT * FROM passwd ORDER BY username, shell;
SELECT * FROM passwd
     WHERE homedir LIKE '/home%'
     -- % is the SQL wildcard character
     AND   shell = '/bin/sh'
     ORDER BY username;
SELECT homedir, projectname
     FROM passwd, projects
     -- assuming we have a projects
     table
     WHERE
     -- this will return the home directory of
       passwd.username = projects.leader;
       -- each project leader for each project
保持数据最新

要更改记录中的字段值,请使用 UPDATE

UPDATE tablename SET field-1 =
value-n WHERE qualification

WHERE 是可选的,但是如果您不指定 WHERE 子句,SQL 将更新所有记录,这显然是“错误的事情”。

假设 Fred Mbogo 想要更改他的 shell。此脚本将完成此操作

UPDATE passwd SET shell = '/bin/tcsh' WHERE username = 'fred';
删除记录

要删除记录,只需使用 DELETE

DELETE FROM tablename WHERE qualifier

就像 UPDATE 一样,WHERE 是可选的,但您可能仍然需要它。假设 Fred 多次冒犯了他的系统管理员

DELETE FROM passwd WHERE username = 'fred';
您需要了解的所有 PHP3 知识(并非真的如此)

在线 PHP3 手册 https://php.ac.cn/manual/ 是一个很好的参考资料,在您创建自己的数据库 Web 应用程序之前,它将是必要的阅读材料。此外,它是一个数据库支持的网站,并且有很多用户评论。在这里,我们将仅检查最基本的 PHP3 功能。

使用 PHP3

这是一个简单的 PHP3 程序,演示了一些基本功能。请注意单独的 HTML 和 PHP3 块

<title>Hello, world!</title>
<body>
<?php
  echo("Hello, world!\n");
  echo("<p>\nWhat a <b>bold</b> move this is!\n");
?>
</body>

该程序将向远程浏览器发送以下 HTML

<title>Hello, world!</title>
<body>
Hello, world!
<p>
What a <b>bold</b> move this is!
</body>
一个类似的程序,它接受一个参数,看起来像这样
<title>Hello, world!</title>
<body>
<?php
  echo("Hello, $name!\n");
  echo("<p>\nWhat a <b>bold</b> move this is!\n");
?>
</body>
您将像任何 CGI 脚本一样查看此页面(假设您将其命名为 hello.php3):http://您的主机.net/~fred/hello.php3?name=fred。当然,这假设您的名字是 Fred 并且已将此文件放在您的 /public_html 目录中。
数据库连接

PHP3 提供了许多有用的函数来连接到数据库;阅读这些函数的最佳位置是 www.php.net/manual/ref.pgsql.php3,我们将检查其中的一些函数。

int pg_connect(host, port, options, tty, dbname);

此函数返回一个整数,“连接索引”,您将需要在该连接上进行所有操作。如果无法建立连接,则返回零。

  • int pg_exec(conn, query); 在连接 conn 上执行 SQL 查询 query。返回结果集索引。

  • int pg_numrows(result); 返回结果集 result 中的元组数。

  • array pg_fetch_row(result,返回与结果集 result 的行 row 对应的数值数组。

  • void pg_close(conn); 关闭连接 conn

示例应用程序:多用户地址簿

我们的示例应用程序是一个可以通过 Internet 访问的地址簿。用户使用其姓名和密码登录,并显示一个选项菜单,其中包括浏览和搜索地址簿以及添加新人。对于地址簿中的每个人,数据库存储任意数量的电子邮件地址、电话号码、URL 和邮政地址。此地址簿还具有一些巧妙的功能,例如向新帐户邮寄密码以及电子邮件和 Web 地址的自动 mailtohref 链接。

设计考虑因素

我们已经完成了设计过程的第一步——确定我们的应用程序需要做什么。剩下的就是定义我们的应用程序将在三个迭代中访问的数据

  1. 高级数据模型

  2. 低级数据模型

  3. 一组应用程序用户合法的视图和事务

通常,最初在实体关系图中建模数据库,该图将每个表表示为一个实体,其中包含一组属性,而跨多个表的“连接”或查询由关系表示。即使您不打算费力制作 E-R 图,您也至少应该考虑这些概念;我们将用文字检查地址簿的实体和关系。

我们将使用的低级模型是关系模型,该模型自 1970 年以来就已存在,并且是大多数商业关系数据库(包括 Oracle、Sybase 和 Informix)使用的模型。

最后,我们将定义用户可以看到数据的方式。一旦我们完成了此操作,我们就完成了困难的部分,可以继续进行繁琐的部分——实现。

实体和关系

由于实体关系模型是如此高级的模型,因此低级模型的一些更棘手的问题尚未显现,并且我们的数据模型看起来非常简单。E-R 模型的主要优点在于,它清晰地展示了数据库微观世界——我们建模的真实世界的片段——并且易于外行和软件工程师理解。实体定义如下

  • 用户:这描述了一个地址簿用户。此实体具有唯一的 ID、登录名、密码、“真实姓名”和电子邮件地址。

  • 地址簿:这是一个“弱实体”——特定用户拥有其信息的个人集合。

  • 个人:这描述了一个可以获取地址信息的人。此实体具有名字、中间名首字母、姓氏和朝代标识符(即 Jr.、III 等)。一个人还具有以下各项中的一个或多个:电子邮件地址、邮政地址、电话号码和 URL。

实体之间的关系定义如下

  • 一个 用户 恰好拥有一个 地址簿

  • 一个 地址簿 包含许多 个人

  • 一个 个人 是恰好一个 地址簿 中的条目。

如果您以前考虑过数据库,您可能会问自己,“为什么一个 个人 不能在多个 地址簿 中?难道两个 用户 不能认识同一个 个人 吗?” 这是您在此过程中必须做出的那种设计决策——我选择允许每个用户拥有自己的私有地址簿,并避免共享记录引起的难题。允许多人共享记录就像允许 C 程序中的全局变量可以被任何函数修改一样——当一个函数在其他函数不知情的情况下更改变量的值时,您很容易得到意外的结果。
迁移到关系模型

现在我们需要将我们的高级模型(人们可以理解的模型)迁移到低级模型(我们的数据库管理系统可以理解的模型)。此过程非常简单,尽管数据库设计人员在此阶段仍然有一些选项,包括规范化。规范化(请参阅资源)是量化和衡量关系模型质量的正式过程;与往常一样,权衡是理论质量与性能。

将您的数据模型从实体关系模型迁移到关系模型的快速而肮脏的算法如下

  1. 确保每个实体的每个属性都是原子的。

  2. 为实体的每个可以容纳多个值的属性创建一个表,并定义一个将这些属性绑定到关联实体的连接。

  3. 为每个实体创建表,使用您尚未处理的属性作为字段。如果实体涉及 n:1 关系,请包含与其相关的记录的键作为外键。

这必然是该过程的简化版本;它不处理 m:n 关系或某些其他细节。有关数据模型转换的更完整讨论,请参阅数据库理论教科书。

可以在 ftp.linuxjournal.com/pub/lj/listings/issue67/3475.tgz 找到“完成”的关系模型的 SQL 代码。所有代码均根据 GNU GPL 发布。

视图和操作

其余的设计步骤是确定我们希望授予用户访问和更新数据的哪种功能。这在我们的数据库中可能不是什么大问题,但是如果我们正在设计一个员工数据库呢?如果每个人都知道每天上网冲浪和休息两个小时午餐的人的工资,这可能会在办公室引起很大的不和谐;但是,他们应该能够访问他的姓名、部门和分机号。同样,除非他们是部门秘书或经理,否则他们不应该能够更改该信息。

我们确实希望对我们的地址簿进行一些保护,以便您可以安心地输入您祖母的电子邮件地址,而垃圾邮件发送者无法仅通过访问您的 Web 服务器来获取它。我们也不想用每个记录上的唯一 ID 号等实现细节来打扰用户——这应该是一个用户友好的地址簿。因此,我们将允许以下操作

  • 用户可以从自己的地址簿中检索记录。

  • 用户可以在自己的地址簿中插入和删除记录。

  • 用户将仅看到她需要看到的内容。

为此,我们创建视图。视图可以只是表中的几列,也可以是连接中的几列。在 SQL 中,视图使用 CREATE VIEW 语句定义,该语句从 SELECT 语句创建视图。视图可以像表一样访问,但您不能对其执行插入、更新或删除操作。我们的示例应用程序中的某些视图还使用 PostgreSQL 函数来使最终应用程序编程更容易,例如,“从该电子邮件地址创建 mailto URL”。

我们还注意到我们无法使用视图强制执行的约束:例如,只能查看自己的地址簿的考虑。我们必须在应用程序程序中实现这些约束。

实现

在 PHP3 中实现非常简单;示例代码中的许多内容是不言自明的,其他内容也得到了很好的注释。

示例应用程序的源代码旨在更多地作为一种教学工具,而不是成品。它可以很好地工作,但在从中创建大型服务之前,您肯定会想要添加功能。我已根据 GNU GPL 发布了它,因此请随意修改我的代码并与他人分享您的修改。此代码也在上面显示的 FTP 站点上。

资源

Will Benton 可以通过 wcb@ccil.org 联系

加载 Disqus 评论