说起来“preparecommand”这事儿,也是被逼出来的。当时搞一个老系统维护,那代码简直没法看,尤其是数据库操作那块儿,乱七八糟的。
问题的开始
一开始接手的时候,发现程序跑起来特别慢,时不时还报错。查半天,发现很多地方都是直接拼接SQL语句去执行,参数什么的直接嵌在字符串里。你想想,这能不出问题吗?慢不说,还容易被注入,简直是个坑。
我当时就想,这不行,得改。最开始想着,是不是数据库连接池有问题,或者索引没建查一圈,这些基础的东西倒是没太大毛病。问题就出在执行SQL命令这块儿。
瞎折腾阶段
那时候网上搜些资料,也看看一些所谓的“最佳实践”。很多人说用ORM框架什么的,但那个老系统,你懂的,牵一发动全身,根本不敢大改。只能在现有基础上优化。
我就试着把拼接SQL改成用参数化查询。找些例子,照着敲。比如原来是 "SELECT FROM users WHERE name = '" + userName + "'"
, 我就改成用 SqlCommand
对象,然后 *("@name", userName)
这种。
改几个地方,好像是好点,但还是觉得别扭。代码看起来还是有点啰嗦,每个查询都要创建命令、添加参数、设置连接、执行,感觉重复劳动特别多。
而且有时候,一个操作里可能要执行好几条SQL,事务处理也搞得麻烦。要么忘开事务,要么忘提交,数据搞错过几次,真是头大。
琢磨“准备”命令
后来我就琢磨,这个执行命令的过程,能不能再优化一下?每次执行前,都要做好多准备工作:指定要执行的SQL或者存储过程、把参数一个个准备好、告诉它用哪个数据库连接、是不是在某个事务里执行等等。
我就想,能不能把这些“准备工作”给统一管理起来?就像做饭前备菜一样,先把所有材料切好、配下锅的时候才不至于手忙脚乱。
然后我就开始捣鼓这个思路。很多数据库操作的库(像.NET里的SqlCommand)都提供类似的功能,只是我们平时可能没太注意或者没用核心思想就是,在真正执行(比如ExecuteReader
, ExecuteNonQuery
)之前,把Command
对象彻底“准备”
我的实践过程
具体我是怎么做的?
- 封装一个“准备”函数: 我没用啥高深的技术,就是写个简单的帮助方法。这个方法接收几个关键信息:要执行的SQL语句或存储过程名、参数列表、数据库连接对象,还有可选的事务对象。
- 内部处理: 在这个方法内部,我先创建一个
Command
对象。然后,把传进来的连接赋给它,如果有事务,也赋给它。最关键的是处理参数,循环遍历传进来的参数列表,一个个Add
到里面。确保类型、大小都设置对。 - 返回“准备好”的命令: 这个方法返回一个已经配置完毕的
Command
对象。
这样一来,在业务代码里,我只需要调用这个帮助方法,传入必要的参数,就能得到一个“万事俱备”的Command
对象。然后直接调用它的执行方法(ExecuteNonQuery
之类的)就行,代码一下子清爽很多。
举个例子: 以前可能要写七八行代码来准备和执行一个命令,现在可能只需要两三行:
var cmd = PrepareMyCommand("usp_UpdateUserData", userParams, conn, transaction);
是不是简单多?
结果和感受
这么改之后,效果还是挺明显的。
- 代码清晰: 重复的准备代码被封装起来,业务逻辑更突出。
- 错误减少: 参数处理、连接、事务这些容易出错的地方被统一管理,不那么容易遗漏。
- 维护方便: 如果以后要改数据库操作的某些通用逻辑(比如加个日志什么的),只需要修改那个帮助方法就行。
说白,这个“preparecommand”的过程,就是把原来分散、重复的准备工作,集中起来,规范化处理。虽然听起来好像没啥技术含量,就是个封装,但在实际项目里,特别是维护老项目时,这种简单的改进往往能解决大问题。
有时候我们总想着用什么新框架、新技术去解决问题,但回过头来看看,把基础的东西用好、做到位,可能效果更直接。这回折腾“preparecommand”的过程,也算是给我自己提个醒。