简化数据库操作与防止SQL注入

在进行经典ASP开发时,经常需要处理数据库查询和事务,同时还要考虑到安全性,特别是防止SQL注入攻击。本文将介绍一个自定义的VBScript类,它封装了数据库操作,简化了参数化查询的创建,并且提供了易于使用的接口。

在处理经典ASP项目时,需要一种管理参数化查询的方法。一方面,需要保护应用程序免受SQL注入攻击;另一方面,需要将数据库访问代码集中到应用程序的一个中心点,以简化维护。

标准方法的问题

在ASP中,创建预处理语句/参数化查询的标准指导是构建一系列语句来创建参数对象,然后将它们附加到命令上,最后执行命令。这个过程通常很繁琐,而且容易出错。

解决方案

创建了一个简单的自定义VBScript类,它将所有必要的数据库操作封装到一个对象中,并且只暴露那些实际需要的项。ADO是一个多功能工具,但实际上从未使用过它的所有功能,所以类只暴露了实际发现有用的功能。

使用代码

该类通过包装ADO连接对象并公开开发者友好的方法来工作。默认实现是保持连接对象私有,不公开,但如果愿意,当然可以将其公开,以便直接访问底层连接。

初始化

首先实例化类并用连接字符串初始化它:

dim DB set DB = new Database_Class DB.Initialize "YOUR_CONNECTION_STRING_HERE"

类内部保留了连接字符串,并通过一个名为Connection的私有函数引用连接。这个函数反过来在需要时创建连接。这允许在实际需要时才懒加载连接,这意味着可以全局包含这个类,连接只会在实际需要时创建。

执行SQL

使用ADO执行SQL时,通常有两种“模式”:执行返回数据的SQL或执行写入数据的SQL。这自然导致公开两种不同的方法,Query()和Execute()。每个方法都接受一个SQL语句和一个或多个参数(通过标量或数组参数传递的开发者友好用法),绑定参数并执行SQL。两者之间的唯一区别是Query()返回一个记录集,而Execute()不返回;事实上,Execute()内部调用Query()但丢弃结果。在SQL中使用?字符标识参数。

无参数查询

以下查询未使用参数:

set rs = DB.Query("select table_name, column_name, is_nullable from master.information_schema.columns", empty)

这里使用了VBScript关键字empty来表示没有传递参数。这使能够拥有相当灵活的方法,尽管不能有真正的函数重载。此方法的返回值是一个记录集,正如预期的那样。

带参数的查询

Query()方法具有开发者友好的特性,它可以接受任意数量的SQL参数。它通过检查第二个参数的类型来实现这一点。如果第二个参数不是数组,那么Query()方法在执行前内部将这个单一参数转换为数组。这使能够避免在不需要时使用丑陋的Array(...)语法。

带一个参数的查询

set rs = DB.Query("select table_name, column_name, is_nullable from master.information_schema.columns where column_name like ?", "x%")

带两个参数的查询

这里是参数类型变化的地方:

set rs = DB.Query("select table_name, column_name, is_nullable from master.information_schema.columns where column_name like ? and is_nullable = ?", Array("x%", "YES"))

这里,将数组作为第二个参数传递给Query()。数组包含将绑定到SQL的每个参数,按照SQL字符串中问号出现的顺序。可以使用数组结构传递几乎无限数量的参数。

执行无返回值的SQL

Execute()看起来与Query()完全相同,除了它是一个Sub,因此没有返回值:

DB.Execute "update foo set a = ?", "b" DB.Execute "insert into foo values (?, ?, ?)", Array(1, "two", "three")

分页查询

通过扩展Query()方法来接受更多参数,可以很容易地管理分页(即简单的记录集分页)。签名如下:

PagedQuery(sql, params, per_page, page_num, ByRef page_count, ByRef record_count)

前两个参数与前两种方法完全相同。per_page是要返回的每页记录数。page_num是想在记录集中查看的当前页码。page_count是一个返回参数,将包含记录集中的总页数。record_count也是一个返回参数,将包含所有页面中的总记录数。

事务处理

由于该类包装了一个连接对象,因此很自然地也要公开事务功能。本可以公开连接对象本身,但如果不需要额外的功能,为什么要这样做呢?以下是一个完全虚构的例子,用于演示过程。

dim sql, rs dim order_type = "some type" dim order_name = "some name" DB.BeginTransaction sql = "insert into orders (order_type, order_name) values (?, ?)" DB.Execute sql, Array(order_type, order_name) sql = "select max(id) from orders where order_type = ? and order_name = ?" dim new_id : new_id = DB.Query(sql, Array(order_type, order_name))(0) DB.Execute "insert into order_status (order_id, status) values (?, ?)", Array(new_id, "N") DB.CommitTransaction

可以随时调用DB.RollbackTransaction来取消事务,这是预期的。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485