访问 http://code.google.com/p/naturaljs/on Google Code 可获取 JScript快速开发框架 Edk 之源码。
相信许多人学 Web 的第一步便是从连接数据开始,封装过程亦如是。连接数据库这道板斧似乎不用多说,无非那个几个步骤,但还是有必要讲讲的。于是,在介绍代码之前,先重温调用数据的一般执行步骤。一、连接数据库;二、执行 SQL 操作。三、返回数据集合。这里有个优化的地方,也是常见的技巧,就是其中连接了的数据库应该对象不应立刻销毁,应予保留,以便反复使用。因为一次请求就会有不止一次的查询,所以保留下来就可以重复使用链接对象。但是为什么不使用链接池(Pool)?由于 JScript 不是守护进程,一次请求关闭一个线程,立刻回收率代码“所占”的资源,所以连接池的算法可能就无用武之地了。
学习了 Java,想起来 Java 的那些概念,所以,连类名称都叫做 DAO。DAO = Data Access Object 数据访问对象,既可读又可写的。当前 DAO 不包含读写的操作,而是分别由 $$.sql.writer.Writer 和 $$.sql.reader.Reader 提供数据库数据的写、读。
DAO 的核心方法为 execute():
/** * 执行SQL语句,返回结果集合。 * @param {String} sql 要执行的 SQL 语句(可选的)。 * @param {Objcec} cfg 配置对象,可以包含连接数据库的字符串(可选的) * @return {ADODB.Recordset} 数据库记录集合。 */ execute : (function(){ var connectObj; // 数据库连接对象,只设置一次,无须重复创建。多次创建则浪费。 function connectDB(cfg){ var dbType // 链接数据库的类型,是Access呢?MySQL呢?还是SQLServer? ,dbPath // 数据库文件的路径,如果通过磁盘访问的话。 ,connectStr // 链接数据库的字符串。 // 数据库对象,同前面的 var connectObj; 的对象 ,connectObj = new ActiveXObject("ADODB.CONNECTION"); if(cfg){ dbType = cfg.dbType; }else if(!cfg && $$.cfg){ dbType = $$.cfg.edk_dbType.toLowerCase(); }else{ dbType = 'access'; // 默认为MS Access数据库 } switch(dbType){ case 'access' : if(cfg && cfg.dbPath){ dbPath = cfg.dbPath; }else if(!cfg && $$.cfg){ dbPath = $$.cfg.edk_isDebugging ? $$.cfg.edk_dbFilePath_Test : Server.mappath($$.cfg.edk_dbFilePath); } connectStr = "DBQ={0};DefaultDir=;DRIVER={Microsoft Access Driver (*.mdb)};".format(dbPath); break; case 'sqlservver' : throw '尚未实现'; case 'mysql' : // 使用driver模式不用有空格在连接字符串中 connectStr = "DRIVER={mysql odbc 5.1 driver};" + "SERVER={0};" + "PORT={1};" + "UID={2};" + "PASSWORD={3};" + "DATABASE={4};" + "OPTION=3"; connectStr = connectStr.format('localhost', 3306, 'root' ,'123', 'test'); break; case 'sqlite' : dbPath = 'd://test.db'; connectStr = "DRIVER={SQLite3 ODBC Driver};Database=" + dbPath; break; default: throw '非法数据库连接类型!'; }; connectObj.open(connectStr); return connectObj; } return function(sql, cfg){ if(!connectObj){ connectObj = this.execute.connectObj = connectDB(cfg); // call only once. } return sql ? connectObj.execute(sql) : connectObj; } })()
有没有发现,connect Object 只创建一次?以后反复调用这个单例。但值得一提的是,我们把数据库连接对象 connect Object 的引用定义在 execute() 方法上!——仔细想想,也没有什么好奇怪的。利用 JavaScript 的超强动态绑定,甚至可以在方法上定义新的元素,建立新的引用,也是毫无问题。现在我们就是利用这个方法设计 DAO 的数据结构。(当然,一些情况就不是“毫无问题”的,例如客户端编程中,DOM 对象千万就不要绑定其他内容,否则极容易 Memory Leak,保持其 DOM API怎么样就怎么样便对了)。这样,我们的数据库连接对象不直接依附在 this 对象上,而是通过 this.execute.connectObj 才可访问。目的就是避免 this 的 API 太乱。
关于静态属性
上述定义一个 connect Object 可以说进行了一次静态属性的分配。为什么是静态的而不是动态的呢?静态的原因在于值只要分配一次;实例属性则依据每次实例化不同而有不同的值。不过 JavaScript 的 OO 特性并不明显,很难从代码中说明我那个地方就是要“静态属性”的,呵呵。怎么搞才好?我开始的做法是写一个 getStaticProerty() 函数,经过这个函数来返回属性,而不是直接获取的。但是这个方法虽然封装是封装了,不过我后来却否决了这个想法,原因就在于一个问题,原本实现静态的逻辑很简单,就是设置一个变量,然后判断是否 undefined,如 undefined 则写入静态值,然后以后不断地访问这个函数就返回静态值。很简单没有什么复杂的地方。特别另外地写一个 getStaticProerty() 函数,反而带来额外的维护成本,也不见得复用性有多么的明显,于是,决定以后在具体实现中实现“静态属性”的思想就可以了,不搞什么 getStaticProerty()。
不过为了更加说明问题所在,我列一列原来的方案,方便理解我说的话:
// 定义函数对象的新方法,即获取属性的函数。 Function.prototype.getStaticProperty = function(setHandler){ function getFunctionName(fn){ return (/function/s+(/w+)/s*/(/)/.exec(fn + ''))[1]; } var propertyName = getFunctionName(setHandler); if(typeof (this[propertyName]) == 'undefined'){ this[propertyName] = setHandler.call(this); return this[propertyName]; }else{ return this[propertyName]; } } // 比较一下这个execute() this.execute = function (sql, cfg){ // call only once. var connectObj = this.execute.getStaticProperty(function connectObj(){ var dbPath ,dbType = ($$.cfg ? $$.cfg.edk_dbType.toLowerCase() : null) || (cfg ? cfg.dbType : null) || 'access' // 如不指定默认为Access数据库。 ,connectString ,connectObj; switch(dbType){ case 'mysql' : // 使用driver模式不用有空格在连接字符串中 connectString = "DRIVER={mysql odbc 5.1 driver};" + "SERVER={0};" + "PORT={1};" + "UID={2};" + "PASSWORD={3};" + "DATABASE={4};" + "OPTION=3"; connectString = connectString.format('localhost', 3306, 'root' ,'123', 'test'); break; case 'access' : if(cfg && cfg.dbPath){ dbPath = cfg.dbPath; }else{ if($$.cfg){ dbPath = $$.cfg.edk_isDebugging ? $$.cfg.edk_dbFilePath_Test : Server.mappath($$.cfg.edk_dbFilePath); } } connectString = "DBQ={0};DefaultDir=;DRIVER={Microsoft Access Driver (*.mdb)};".format(dbPath); break; case 'sqlite' : connectString = "DRIVER={SQLite3 ODBC Driver};Database=d://test.db"; break; default: throw '非法数据库连接类型!'; }; connectObj = new ActiveXObject("ADODB.CONNECTION"); connectObj.open(connectString); return connectObj; }); return sql ? connectObj.execute(sql) : connectObj; }
当前 DAO 支持 Access、MySQL(要装ODBC驱动)和 SQLite(同样,也要装驱动)的字符连接串。
针对已查询出来的记录集,是不是要再经过一定的处理呢?是的。我们是 JS 的语言,就转换为转换为天然的 JSON 结构吧。过程很简单,就是双循环 RecordSet。其中要注意的就是 ADO RecordSet 数量类型变为JS类型,即 sql2json(value, dataType) 函数。当前我选择一个简单的转换,如果要复杂、全面的 ADO 类型检测,可以通过请查阅文件: adovbs.inc 了解更多常量信息,该文件在系统中默认的路径为: C:/Program Files/Common Files/System/ado/adovbs.inc,或者直接看看我后面贴的函数。这个函数很全面的了,但我用不上很多的地方,所以就进行简化,于是得到当前的 sql2json。
/** * 针对已查询出来的记录集,转换为 JavaScript 的 JSON 结构。 * @param {RecroedSet} rs ADO 对象集合。 * @return {Array} 输出的 JSON 数组。 */ ,row2json : (function(){ /** * 转换值的 ADO 类型为 JavaScript 类型。 * @private * @param {Number} adoField * @param {Any} value * @return {Any} */ function sql2json(value, dataType){ switch(dataType){ case "string": case 129: // 'char': case 130: // 'nchar': case 200: // 'varchar': case 201: // 'text': case 202: // 'nvarchar': case 203: // 'ntext': return String(value); case "number": case 20: // 'bigint': case 2: // 'smallint': case 16: // 'tinyint': case 14: // 'decimal': case 5: // 'double': return Number(value); case 11: // "boolean": case 'bol': return Boolean(value); case "date": case "object": case 135: // 'datetime': case 64: // 'fileTime': case 134: case 137: return new Date(value).format("yyyy-mm-dd"); } } return function(rs){ var output = [] // Array 输出的 JSON 数组 ,rawArr = rs.getRows().toArray() // Array getRows() 返回的数组 ,fields = rs.Fields // comObj rs.Fields 字段对象,每一列都不同。 ,fLen = fields.Count // int 字段总数 ,len = rawArr.length / fLen // int 记录数 ,sp // int 位数 ,row; // object 构成 JSON 的行对象 // 自动关闭 RecordSet 集合,可以尽快释放 RecordSet 对象占用的资源。 rs.Close(); for (var i = 0; i < len; i++){ sp = i * fLen ,row = {}; for (var j = 0; j < fLen; j++){ row[fields(j).name] = sql2json(rawArr[sp + j], fields(j).Type); } output.push(row); } return output; } })()
文章最后谨记:在程序写完阶段关闭数据库连接,即 $$.sql.close();
附:
/** * 转换值的 ADO 类型为 JavaScript 类型。 * 更多常量信息请查阅文件: adovbs.inc ,该文件在系统中默认的路径为: * C:/Program Files/Common Files/System/ado/adovbs.inc * 其他系统请自行搜索。 * @private * @param {Any} value * @param {RecordSet.Field} adoField * @return {Any} */ sql2json = function(value, adoField){ // Base Types var BASE_TYPE_UNKNOWN = 0; var BASE_TYPE_NUMBER = 1; var BASE_TYPE_DATE = 2; var BASE_TYPE_BOOLEAN = 3; var BASE_TYPE_TEXT = 4; // ADO Types var ADO_TYPE_INT = 3; var ADO_TYPE_FLOAT = 5; var ADO_TYPE_MONEY = 6; var ADO_TYPE_DATETIME = 7; var ADO_TYPE_BIT = 11; var ADO_TYPE_VARCHAR = 200; var ADO_TYPE_TEXT = 201; var ADO_TYPE_NVARCHAR = 202; var ADO_TYPE_NTEXT = 203; var __adoFieldTypes = { 20: { name: "adBigInt", type: BASE_TYPE_NUMBER }, 128: { name: "adBinary", type: BASE_TYPE_UNKNOWN }, 11: { name: "adBoolean", type: BASE_TYPE_BOOLEAN }, 8: { name: "adBSTR", type: BASE_TYPE_UNKNOWN }, 136: { name: "adChapter", type: BASE_TYPE_UNKNOWN }, 129: { name: "adChar", type: BASE_TYPE_TEXT }, 6: { name: "adCurrency", type: BASE_TYPE_NUMBER }, 7: { name: "adDate", type: BASE_TYPE_DATE }, 133: { name: "adDBDate", type: BASE_TYPE_DATE }, 137: { name: "adDBFileTime", type: BASE_TYPE_DATE }, 134: { name: "adDBTime", type: BASE_TYPE_DATE }, 135: { name: "adDBTimeStamp", type: BASE_TYPE_DATE }, 14: { name: "adDecimal", type: BASE_TYPE_NUMBER }, 5: { name: "adDouble", type: BASE_TYPE_NUMBER }, 0: { name: "adEmpty", type: BASE_TYPE_UNKNOWN }, 10: { name: "adError", type: BASE_TYPE_UNKNOWN }, 64: { name: "adFileTime", type: BASE_TYPE_DATE }, 72: { name: "adGUID", type: BASE_TYPE_UNKNOWN }, 9: { name: "adIDispatch", type: BASE_TYPE_UNKNOWN }, 3: { name: "adInteger", type: BASE_TYPE_NUMBER }, 13: { name: "adIUnknown", type: BASE_TYPE_UNKNOWN }, 205: { name: "adLongVarBinary", type: BASE_TYPE_UNKNOWN }, 201: { name: "adLongVarChar", type: BASE_TYPE_TEXT }, 203: { name: "adLongVarWChar", type: BASE_TYPE_TEXT }, 131: { name: "adNumeric", type: BASE_TYPE_NUMBER }, 138: { name: "adPropVariant", type: BASE_TYPE_UNKNOWN }, 4: { name: "adSingle", type: BASE_TYPE_NUMBER }, 2: { name: "adSmallInt", type: BASE_TYPE_NUMBER }, 16: { name: "adTinyInt", type: BASE_TYPE_NUMBER }, 21: { name: "adUnsignedBigInt", type: BASE_TYPE_NUMBER }, 19: { name: "adUnsignedInt", type: BASE_TYPE_NUMBER }, 18: { name: "adUnsignedSmallInt", type: BASE_TYPE_NUMBER }, 17: { name: "adUnsignedTinyInt", type: BASE_TYPE_NUMBER }, 132: { name: "adUserDefined", type: BASE_TYPE_UNKNOWN }, 204: { name: "adVarBinary", type: BASE_TYPE_UNKNOWN }, 200: { name: "adVarChar", type: BASE_TYPE_TEXT }, 12: { name: "adVariant", type: BASE_TYPE_UNKNOWN }, 139: { name: "adVarNumeric", type: BASE_TYPE_NUMBER }, 202: { name: "adVarWChar", type: BASE_TYPE_TEXT }, 130: { name: "adWChar", type: BASE_TYPE_TEXT }, 8192: { name: "adArray", type: BASE_TYPE_UNKNOWN } }; // __getValueByType() function __getValueByType(type, value) { if (typeof value === "undefined") return null; switch (type) { case "string" : case BASE_TYPE_TEXT : return String(value); case "number" : case BASE_TYPE_NUMBER : return Number(value); case "boolean" : case BASE_TYPE_BOOLEAN : return Boolean(value); case "date" : case "object" : case BASE_TYPE_DATE : return new Date(value).format("yyyy-mm-dd"); default : return value; } } return __getValueByType(__adoFieldTypes[adoField.Type].type, value); }
附:
在 ASP 程序中使用参数化查询
ASP 环境下的参数化查询主要由 Connection 对象和 Command 对象完成。
Access 数据库只支持匿名参数,在传入参数的位置用问号代替即可。SQL Server 数据库虽然支持匿名和非匿名的参数,但是在 ASP 中也仅能使用匿名参数。
var conn = Server.CreateObject("ADODB.Connection"); conn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Server.MapPath("Test.mdb"); conn.Open(); var cmd = Server.CreateObject("ADODB.Command"); cmd.ActiveConnection = conn; cmd.CommandType = 1; cmd.CommandText = "SELECT TOP 1 * FROM [User] WHERE UserName = ? AND Password = ?"; cmd.Parameters.Append(cmd.CreateParameter("@UserName", 200, 1, 20, "user01")); cmd.Parameters.Append(cmd.CreateParameter("@Password", 200, 1, 16, "123456")); var rs = cmd.Execute(); Response.Write(rs("UserId").value); rs.Close(); conn.Close();http://heeroluo.net/Article/Detail/61