三层架构已是非常经典的架构,稍微对开发有了解的人都知晓,为了解耦而把代码分在表示层(UI)、业务逻辑层(BLL)和数据连接层(DAL)。在我懵懂之期,把对数据库链接创建、参数传入和执行SQL这些操作都放到了DAL层,而把SQL编写,参数绑定、还有结果获取这些都塞在BLL层。这是不对的,在我后来接触到真正的系统开发时,首次听到SQLHelper这个概念才慢慢对DAL层有了真正的认识。本篇专门讨论我见过的各种DAL层的底层,这个底层主要是封装了每次都数据库执行查询(不一定是SELECT、还包括其他DDL甚至DML)所必要的数据库连接处理,开闭链接,事务处理等操作。
SQLHelper
如果一个DAL的底层按最懒最粗略来说,可以从网上找一个SQLHelper,按照自己的习惯去修改用到项目中。SQLHelper是微软一个开源项目PET Shop里面实现了对SQL Server数据库操作。实际上是对DbComand的ExecuteNonQuery、ExecuteReader和ExecuteScalar方法进行了封装,免除了每次查询都要开发人员去打开、关闭数据库链接,为DbCommand绑定DbParameter。
当然按我自己的偏好,原始的SQLHelper封装的方法对我来说调用起来就相对繁琐,于是我针对这类方法都重载了,免除了ConectionString,CommandType这些参数,同时也对执行SELECT查询获取一个DataTable这样的操作多封装了一类方法:ExecuteTable,这样查询起来就不用每次从DbDataReader中获取数据了。
实习时遇到的第一个框架的DAL
这个DAL的底层我最开始看不明白,被借用到我的毕业设计里面。到最近看代码的时候找回来看,最开始还是看不怎么明白,后来突然阔然开朗。
有三个类DBUnitBase+DBUnit+DBControler,DBUnitBase是基类,DBUnit是继承了DBUnitBase,DBControler是有一个字段是DBUnit类型。他们的职责分别是
-
DBUnitBase:数据库工具类的基类,包含了一个数据库连接,封装了对数据库的所有操作,包含打开/关闭数据库、ExecuteNonQuery、ExecuteReader,此外包含了额外的执行SQL脚本。
-
DBUnit:继承了DBUnitBase,额外定义了根据给定的链接字符串或者从配置文件中的链接字符串中创建一个数据库连接。
-
DBContoler:负责对包含的数据库链接进行操控,这个操控是在事务层面上的,因为每次查询数据库都是包含在一个事务中,具体的方法包括开启事务BeginTransaction、提交事务CommitTransaction和回滚事务RollbackTransaction。
个人认为这几个类是SQLHelper类的改装+事务控制而已。DBUnitBase虽然是基类,但对于SQLHelper的方法全部都实现了,而且只针对一种数据库,它的子类DBUnit只负责对DBUnit的构造和链接创建,两者之间只是职责分离,单纯是对象构造和功能使用这两方面的分离,并没有把数据库的操作抽象出来,为各种数据库的具体提供模板,这使得扩展性比较低,可能是不考虑使用其他数据库的原因吧!DBContoler这个类给我感觉抽象性还不错,因为我不用管它究竟是哪一种数据库,它单纯负责把数据库的事务打开、提交或回滚。
自己参与搭建的框架的DAL
这个是我工作两年后首次参与搭建的框架,当时因为有个同事提了要兼容Access数据库这个需求,于是我就干脆把MySQL、SQLite和Oracle都兼容进去了,槽点多多,也是先看看里面包括的类和他们的关系,这里涉及的类比较多
这个底层的思路是这样的:我把各种类型数据库的查询操作都抽象到一个接口中:IDBHelper,各种具体数据库都对实现这个接口,从而对具体数据库操作的进行实现,而每种数据库的查询参数类型都不一样,于是我额外定义了一个公用的参数类,由DbParameterConventer转换成各种数据库的参数,具体的转换操作由对应数据库的ParameterConvert进行转换。查询时传入的SQL语句会通过一个SQLCommandAdapter传入,实际上他一个包含了四个字符串类型属性的类而已,每个字段代表了一个数据库类型,想要查询数据库时,传入这次查询中用到的各种数据库对应的SQL,连同参数传入到BaseDAL_Ex中,BaseDAL_Ex的会按照这次传入的SQL通过DBHelperProvider按需选择数据库Helper和ParamterConvert。查询时用到的链接字符串均从配置文件中获取,这些链接信息经过DBConnectConfigHandler读取分析成ConnectInfo,存储在ConnectInfoCollector中,在BaseDAL_Ex的各种方法都重载了一个需要提供链接名的版本,按照这个链接名去寻找相应的链接字符串,否则就去使用默认的链接字符串进行数据库连接。
这个DAL底层的缺陷在于仍然缺乏面向对象编程的意识,虽然把公共部分抽象成接口或抽象类,但是在匹配调用的时候还是用了大量的If…Else去判断,没有借助工厂模式去减缩,虽然在一定程度上我和另一位同事都认为不可缺少If..Else的判断,但是这里的If…Else中的代码量过大,有必要去减缩一下。
和其他公司交流时遇到的数据层
这个底层出自于某公司的大牛之手,这个DAL底层已经是一个精简版的ORM,我看代码就只看了ADO.NET那一部分,ORM那部分还没时间看,自己想把与以上几个都是属于纯ADO.NET范畴内的先对比总结一下,弄清楚了再走下一步。
这里主要是用了微软的企业库EntLib,在结合了几个类:Dao,GenericDao,DaoFactory,DatabaseDao。最开始看这份源码的时候我头脑有点绕晕,由于当时也不太了解EntLib,一直找不到DbConnection这个类所在地,下面就介绍一下各种类的结构。
-
Dao:数据持久层的入口,是一个抽象类,也使用了单例模式,把继承它的一个具体类外放出来,调用Dao所定义的所有抽象方法,Dao中定义的抽象方法则包括ADO.NET部分使用的ExecuteNonQuery、ExecuteProcedure、QueryReader等,还有ORM的一些方法。这些方法供继承它的具体类进行重写。
-
GenericDao:继承了Dao抽象类,重写了Dao中所有定义的虚方法,也就是实现了数据库查询方法,这个查询是广义的查询,但是这个实现只是一种外壳实现罢了,真正的查询方法则又放到了抽象方法里面留给子类去实现,这些外壳实现的查询方法统一调用了方法去获取链接,生成SQL语句,具体的执行操作就由虚方法去执行,执行查询后按需要返回的类型返回int则是int,返回datatable则是datatable,返回datareader则返回datareader,因为这是一个泛型方法,各种查询的差异它不需要去关心,这些细节都是由子类去实现的。
-
DatabaseDao:这个类继承了GenericDao,就是实现了由GenericDao定义的虚方法,这些虚方法都是负责根据给定的DbCommand和SQL去执行查询,应为执行的方式可能有差异,而这个DatabaseDao是针对了企业库数据访问框架去实现的,所以这里实现的查询方法实现形式都按照企业库的方式去一一实现。
-
DaoFactory:这个类构造了EntLib企业库的DataBase,也是作为了Dao的工厂去构造Dao。同时是DAL层中的缓存器,其中的一个缓存内容就是Dao。当抽象的Dao在外部被单例访问调用数据库查询时,Dao就会调用这个DaoFactory去构造相应的Dao去执行这个查询(在这个项目里面暂时就只有使用了企业库的DatabaseDao),查询的方法一如既往地去处理SQL语句和参数,需要数据库连接了,又通过DaoFactory获取Dao(这里其实也有可能包含了构造新对象的可能,但一般不会了,因为在第一次调用工厂时已经构造了)里面的数据库连接,最终实现数据查询。
在这几个类相互交互中感觉被弄得团团转,因为单纯Dao有三重继承,而这三个类都与DaoFacotry交互,通常在调用的过程中又会涉及到Dao的调用,所以调用方霎时又成了被动方。不过还一个角度去看的话貌似会清晰一点,他们各自都履行了各自的职责,
-
Dao这个类角色最为复杂,因为它本身是基类,提供了外部访问Dao的方法,在自身又要规定Dao需要做的事情,而且又要对数据库进行保存。专属的事就是提供了外部访问Dao的方法。
-
接着实现要Dao做得是由DenericDao进行初步实现,它只是抽取了各种操作中必须进行的操作统一去实现了,而各种操作中存在差异的,可能会出现不同实现形式的,就外放到它的子类去做了。
-
所以DatabaseDao是实现这种差异性数据库查询的一个子类,按现在来说暂时就单纯这种实现,它这一套是使用了企业库的。
-
在Dao调用方和Dao自身都不涉及到Dao的构造和数据库的构造,因为这些都是由DataFactory去执行,这里我认为是使用了控制反转的思想。
在这个框架里面的亮点是用了微软的EntLib,这个免除了一大堆各种数据库类型的DbHelper和DbParameter的兼容转换。但我强迫症地认为还是使用了If…Else的判断,估计这个是没法避免了,即便是用了工厂。对于这个框架可能我没了解透,有内容理解错了还是有可能的,这将在后续更改,阅读这个框架的剩余内容有新发现的也会在继续写写博文。