您想快速构建一个的确很酷的 Web 2.0 JavaScript 应用程序原型,但又不想将您的所有时间都耗费在编写连接代码以访问数据库吗?以前,为了获取从数据库到前端的所有传输方法的数据,要使用所有创建、读取、更新和删除(CRUD)方法为数据库中的每个表编写一个类。然后,需要在那些类之上放置一些编组代码,以便为前端提供一个访问层。最后,将一些 JavaScript 库放置到那个访问层之上,以便访问后端。这是多么痛苦的一件事啊!
本文提出一个替代方法:使用单个数据库类包转多个数据库表。单个驱动程序脚本连接前端和后端,前端上的另一个包装类向您提供对所有所需表的访问权。图 1 显示了完整的堆栈。
图 1. JavaScript 数据访问层架构
在样例应用程序中,一个拥有添加新条目能力的清单页是用户界面的基础。这个页使用一个 JavaScript 类通过后端访问数据库。反过来,该 JavaScript 类使用 jQuery 进行 Ajax 调用。
Ajax 请求受到一个 PHP 驱动程序页的保护,该页先确保请求有效,然后将其传递到一个处理该请求的数据库包装类。包装器返回一些结果,驱动程序将它们格式化为 JSON 并返回浏览器。
这种方法有其局限性。首先,跨多个相关表工作不太好,因为每个请求都是基于每个表而创建的。要修复这个问题,可以添加新的自定义方法跨多个表进行相关请求。第二个问题是数据安全性,没有任何角色或用户访问检查来确保适当的数据访问。这个问题也可以修复, 方法是添加一些代码来处理用户和角色。
这个代码提供的是一种快速、轻松和安全的方法,将数据从数据库传递到用户界面中,从而快速构建应用程序原型。此外,它还是一个不错的学习工具,可用于学习数据库访问、PHP、Ajax、jQuery 和 JavaScript 等技能。
一切都从数据库开始。在本例中,您使用一个作者及其相关图书数据库。清单 1 显示了数据库的 MySQL 的初始化代码。
清单 1. books.sql
DROP TABLE IF EXISTS authors; CREATE TABLE authors ( id BIGINT NOT NULL PRIMARY KEY auto_increment, name VARCHAR(255) NOT NULL ); DROP TABLE IF EXISTS books; CREATE TABLE books ( id BIGINT NOT NULL PRIMARY KEY auto_increment, author_id INT NOT NULL, name VARCHAR(255) NOT NULL ); INSERT INTO authors VALUES ( 0, 'Jack Herrington' ); INSERT INTO authors VALUES ( 0, 'Dave Smith' ); INSERT INTO authors VALUES ( 0, 'Harold Rollins' ); INSERT INTO books VALUES ( 0, 1, 'Code Generation In Action' ); INSERT INTO books VALUES ( 0, 1, 'Podcasting Hacks' ); INSERT INTO books VALUES ( 0, 2, 'Welcome To The Universe' ); |
要使这段代码进入数据库,首先需要创建一个数据库:
% mysqladmin create books |
然后,将一些表和示例数据添加到数据库:
% mysql books < books.sql |
当然,如果您需要指定数据库用户名和密码,那么您的命令可能略有不同。
要访问数据库,首先要使用 PHP 创建一个 DatabaseWrapper 类。这个类在构造时获取一个数据库连接句柄和一个表名并提供 5 个方法:
-
get
— 如果被给予一个整数 ID,get 方法将返回表中的一行。此方法假定表中的
"id"字段有唯一整数值。 -
getAll
— 此方法返回数据库中的所有记录。它接受一些选项。这里实现的唯一选项是限制,该选项限制返回的记录的计数。如果愿意,可以添加更多选项 — 例如,添加一个
"order"选项返回按一个指定字段排序的记录。 - delete — delete 方法接受一个整数 ID 并从表中删除该行。
- insert — insert 方法在表中插入一个新行。
- update — update 方法接受一个整数 ID,以指定要更新的行;或者接受一个字段哈希表,使用指定的值更新字段。
这 5 个函数可向任何客户机提供一个完整的 CRUD 接口。但它们受到限制,只能在一个表上工作,不能通过关系跨多个表工作。
清单 2 显示了这个数据库封装类的代码。
清单 2. DBWrapper.php
<?php
class DatabaseWrapper {
private $dbh;
public function __construct( $dbh, $table ) {
$this->dbh = $dbh;
$this->table = $table;
}
public function get( $id ) {
$sql = 'SELECT * FROM '.$this->table.' WHERE id = :id';
$sth = $this->dbh->prepare($sql);
$sth->execute(array(':id' => $id));
return $sth->fetchObject();
}
public function getAll( $options = array() ) {
$sql = 'SELECT * FROM '.$this->table;
if ( isset( $options->{'limit'} ) ) {
$sql .= ' LIMIT '.$this->dbh->quote( $options->{'limit'}, PDO::PARAM_INT );
}
$sth = $this->dbh->prepare($sql);
$sth->execute();
return $sth->fetchAll( PDO::FETCH_CLASS );
}
public function delete( $id ) {
$sql = 'DELETE FROM '.$this->table.' WHERE id = :id';
$sth = $this->dbh->prepare($sql);
$sth->execute(array(':id' => $id));
}
public function update( $id, $values ) {
$binds = array( ':id' => $id );
$bindnames = array();
foreach(array_keys($values) as $k) {
$k = $this->clean( $k );
$binds[ ":$k" ] = $values[ $k ];
$bindnames []= "$k=:$k";
}
$bindnames = join( $bindnames, ',' );
$sql = 'UPDATE '.$this->table." SET $bindnames WHERE id=:id";
$sth = $this->dbh->prepare($sql);
$sth->execute( $binds );
}
public function insert( $values ) {
$keys = array();
$binds = array();
$bindnames = array();
foreach(array_keys($values) as $k) {
$k = $this->clean( $k );
$keys []= $k;
$binds[ ":$k" ] = $values[ $k ];
$bindnames []= ":$k";
}
$keys = join( $keys, ',' );
$bindnames = join( $bindnames, ',' );
$sql = 'INSERT INTO '.$this->table." ( $keys ) VALUES ( $bindnames )";
$sth = $this->dbh->prepare($sql);
$sth->execute( $binds );
return $this->dbh->lastInsertId();
}
private function clean( $k ) {
return preg_replace( '[^A-Za-z0-9_]', '', $k );
}
}
?>
|
get 和 getAll 的代码是最直观的。它使用 PHP 的 PDO 库通过连接手柄对数据库执行 SELECT 请求。delete 方法也相当简单,因为它只是执行一个 DELETE 请求。
需要注意的一个重点是在 SQL 语句中使用以冒号界定的替换操作符。这些替换操作符是安全数据库访问的关键。替换操作符确保恶意脚本不能将 SQL 附加到查询中覆盖现有代码,访问它们不应该看到的数据。这种伎俩称为 “SQL 注入”,是非常常见的攻击方式。
insert 和 update 方法要复杂一些,因为它们需要使用用于输入数据的额外字段格式化 SQL 语句。字段名称通过 clean 方法清理,clean 方法返回字段名称中的字符之外的任意字符。这种方法消除了 SQL 注入攻击的可能性。
要使数据传递到浏览器,数据必须可以通过一个 web 页面访问。为此,我们创建一个名为 driver.php 的页面。清单 3 显示了这个 web 页面的代码。
清单 3. driver.php
<?php
include 'dbwrapper.php';
header( 'Content-Type', 'application/json' );
try {
$table = null;
if ( $_GET['table'] == 'books' || $_GET['table'] == 'authors' )
$table = $_GET['table'];
else
throw new Exception( 'Invalid table name' );
$dw = new DatabaseWrapper(
new PDO('mysql:host=localhost;dbname=books', 'root', ''),
$table
);
$additional = array();
foreach( array_keys( $_GET ) as $k ) {
if ( $k != 'table' && $k != 'method' && $k != 'id' ) {
$additional[$k] = $_GET[$k];
}
}
switch( $_GET['method'] ) {
case 'get':
print json_encode( $dw->get( $_GET['id'] ) );
break;
case 'getAll':
print json_encode( $dw->getAll( $additional ) );
break;
case 'insert':
print json_encode( $dw->insert( $additional ) );
break;
case 'update':
$dw->update( $newid, $additional );
print json_encode( true );
break;
case 'delete':
$dw->delete( $_GET['id'] );
print json_encode( true );
break;
default:
throw new Exception( 'Unknown method' );
break;
}
} catch ( Exception $e ) {
print json_encode( array( 'error' => $e->getMessage() ) );
}
?>
|
这个简单的脚本从 web 请求获取 GET 参数并将它们发送到一个 DatabaseWrapper 实例。如果成功,它将结果格式化为 JSON。如果处理过程中出现错误,将抛出一个异常,这个异常也被格式化为 JSON。
为确保这个脚本不能用于访问数据库中的任何表,代码首先检查被请求的表名。在本例中,只有 "authors" 或 "books" 可以访问。如果您将这段代码用于您自己的架构,需要更改代码的相应部分。
代码然后检查这个方法参数,确定调用 5 个动作(get、getAll、delete、insert 和 update)中的哪一个。对于插入,update 和 getAll 还在 GET 数组中发送一组额外的键。
要测试代码,打开一个 web 浏览器并使用各种 URL 参数访问 driver.php 页。图 2 在 URL 指定使用 get 方法访问单个记录时显示。
图 2. 对单个记录的 JSON 请求
图 2 显示被请求的作者行的 JSON 编码的记录。对于作为一个 JavaScript 哈希表编码的记录,JSON 对象包含 "id" 字段和 "name" 字段。
这个请求上的参数是:
driver.php?table=authors&method=get&id=1 |
要获取所有记录,只需更改这个方法,删除 "id" 属性:
driver.php?table=authors&method=getAll |
图 3 显示了这个请求的 JSON 结果:
图 3. 请求所有记录的 JSON 请求
图 3 中有一个返回的 JavaScript 记录的数组,对于数据库中的每一行,数组都包含一个带有 "name" 和 "id" 字段的对象。
毫无疑问,服务器代码的基础对于构建一个 web 接口已经足够。我们下面就开始构建前端。
要从 web 浏览器访问数据库,需要一个通过 Ajax 利用 driver.php 脚本的类。如果不使用 jQuery 或 Prototype.js 这样的 JavaScript 库,Ajax 请求可能有点麻烦。在这里,我们使用 jQuery,但只需对代码稍作更改,就可以使用您喜欢的任何库。
除 jQuery 外,我们还使用了 Class 扩展,该扩展允许轻松定义一个新的 JavaScript 类 — 本例中为 DbWrapper 类,如 清单 4 所示。
清单 4. dbwrapper.js
var DbWrapper = Class.create({
table: '',
get: function( id, callback ) {
$.getJSON( 'driver.php', { table: this.table, method: 'get', id: id }, callback );
},
getAll: function( callback, params ) {
if ( params == null ) params = {};
params.table = this.table;
params.method = 'getAll';
$.getJSON( 'driver.php', params, callback );
},
insertObject: function( params, callback ) {
params.table = this.table;
params.method = 'insert';
$.getJSON( 'driver.php', params, callback );
},
updateObject: function( id, params, callback ) {
params.table = this.table;
params['id'] = id;
params.method = 'insert';
$.getJSON( 'driver.php', params, callback );
},
deleteObject: function( id, callback ) {
$.getJSON( 'driver.php', { table:this.table,
'id':id, method: 'delete' }, callback );
}
});
|
这个简短的类将上述 5 个方法中的每一个封装到后端上。根据所选的方法,每个方法使用一组不同的参数调用 getJSON。另外,每个方法都接受一个处理从请求返回的数据的回调。
insert、update 和 delete 方法的名称分别被更改为 insertObject、updateObject 和 deleteObject,这是因为 delete 是 JavaScript 中的一个保留字。
清单 5 显示了利用这个新 JavaScript 类的页面。
清单 5. Index.html
<html>
<head><title>Database access test</title>
<script src="jquery-1.5.min.js"></script>
<script src="Class-0.0.2.min.js"></script>
<script src="dbwrapper.js"></script>
<script>
var dbw = null;
function updateAuthorsTable() {
dbw.getAll( function( data ) {
$('#authors').html('<table id="authors"><tr><td>ID</td>
<td>Author</td></tr></table>');
$(data).each( function( ind, author ) {
$('#authors tr:last').after('<tr><td>'+author.id+'</td>
<td>'+author.name+'</td></tr>');
} );
} );
}
$(document).ready(function() {
dbw = new DbWrapper();
dbw.table = 'authors';
updateAuthorsTable();
$('#addbutton').click( function() {
dbw.insertObject( { name: $('#authorname').val() },
function( data ) {
updateAuthorsTable();
} );
} );
} );
</script>
</head>
<body>
<table id="authors">
</table>
<h3>Add A New Author</h3>
Name: <input type="text" id="authorname">
<input type="submit" value="Add" id="addbutton">
</body>
</html>
|
这个页面所做的第一件事就是包含 jQuery、Class 扩展以及您的新 DbWrapper.js 文件。然后,当页面载入 ready 函数时,它创建一个 DbWrapper。然后,这个 DbWrapper 实例被 updateAuthorsTable 函数使用,该函数使用这个包装器从数据库请求所有作者。然后,updateAuthorsTable 函数向页面上的一个表添加一些行,显示数据库表的内容。
页面底部有一个作者姓名表单,以及一个 Add 按钮。这个 Add 按钮的脚本调用 DbWrapper 类上的 insertObject 方法,向数据库添加一个新行。那个操作成功后,该脚本调用 updateAuthorsTable 函数,使用这个新行更新显示。
如果一切都设置正确,首次打开这个页面时将看到如 图 4 所示的图像。
图 4. 动态 HTML 列表和添加页面
图 4 显示页面已经连接到服务器端的 driver.php 脚本,请求数据,然后格式化数据以便显示。如果一切正常,这个显示应该显示一列来自数据库的作者、一个输入框、以及一个用于创建新作者记录的 Add 按钮。
下一步是检查添加行的功能是否有效。在本例中,在作者姓名字段中输入 Geraldo Rivera(如 图 5 所示),然后单击 Add。
图 5. 添加一条新记录
Add 按钮的代码调用 DbWrapper 类上的 insertObject 方法,该方法然后调用服务器上的 insert 方法。如果这个步骤成功,则页面上的 JavaScript 使用页面载入过程中首次调用的函数更新作者表。图 6 显示了更新结果。
图 6. 使用新记录更新的页面
图 6 显示应用程序已将作者 “Geraldo Rivera” 添加到作者列表,该作者出现在显示中。图 6 说明,您已对后端数据库拥有读写访问权。要访问一个包含更多表的不同架构,只需将对新表的支持添加到 driver.php 脚本。
我在本文开头提到过,这个代码最好用于快速原型构建而非生产,原因有以下两点:
- 访问非常开放,不受用户、角色和权限限制。
- 如果您的架构中的表间存在一些关系(这种可能性很大),那么这个代码不是特别有效。
另外,这个示例还存在先天不足:不够 RESTful。在 REST 世界中,应用程序不应该以改变应用程序状态(插入、更新和删除请求时发生)的方式响应 GET 请求。但是,创建一个 PHP 服务来处理 POST、DELE 和 REST 需要的所有 URL 重写工作要困难得多。我在这里使用的简单页面仅仅是用于演示目的。
但是,抛开这些警告,这个示例实际上是一个很不错的例子,展示了如何联合使用 PHP、PDO 库和 jQuery 创建一个可移植的 JavaScript 数据访问层。如果您试用这个示例,一定能从中获得收益,尽管它不适用生产环境。
学习
- jQuery 网站:获取快速 web 开发所需的资源,下载 jQuery 库并找到您需要的所有文档。
- jQuery Class 扩展:找到这个方便的扩展,它用于 Google 代码站点上托管的 jQuery 的类创建和管理。
- PHP 网站:访问这个 PHP 最佳参考资料,PHP 是一个应用广泛、目的宽泛的脚本语言,尤其适合 web 开发。
- W3C:访问这个卓越的标准站点;特别是,XML 标准 与本文相关。
- PDO 库:找到关于在 PHP 中访问数据库的所有这些用例的优秀文档等资源。
- 本文作者撰写的更多文章(Jack Herrington,developerWorks,2005 年 3 月至今):阅读关于 Ajax、JSON、PHP、XML 和其他技术的文章。
- developerWorks XML 专区:在 XML 专区获取提高您的专业技能所需的资源。
- 访问 developerWorks Open source 专区,获得丰富的 how-to 信息、工具和项目更新,帮助您用开放源码技术进行开发,并与 IBM 产品结合使用,以及我们 最受欢迎的文章和教程。
- IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
- XML 技术库:访问 developerWorks XML 专区,获得广泛的技术文章和技巧、教程、标准和 IBM 红皮书,并阅读关于 XML 技巧 的更多信息。
- developerWorks 技术活动 和 网络广播:随时关注这些活动中的技术。
- developerWorks 播客:收听面向软件开发人员的有趣访谈和讨论。
- developerWorks
按需演示:观看面向初学者的产品安装和设置演示,以及为经验
丰富的开发人员提供的高级功能。
获得产品和技术
-
IBM 产品评估试用版软件:下载或 IBM SOA Sandbox for People,并开始使用来自 DB2®、Lotus®、Rational®
、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- XML 专区讨论论坛:参与任何一个 XML 相关讨论。
- 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和 wikis,并与其他 developerWorks 用户交流。
