浅谈数据访问层
浅谈数据访问层
写下数据访问层这几个字,恐怕现在的程序员很少知道是什么意思的,他们可能知道数据实体,知道EF和SqlSugar,不知道数据访问层是咋回事。的确现在的ORM框架已经淡化了数据访问层的概念,现在只要会创建实体类,会调用EF就可以了,框架一切都是做好了,不用懂数据库,也不用关心是什么数据库,总之一套代码什么数据库都支持,还为这种开发模式起了名字叫CodeFirst(代码先行)。作为一名老程序员,我不知道软件开发模式为啥会演变到今天这个样子,搞的越来越复杂,越来越脱离根本。
我们那时候的程序员(大概范围是在2000年前后10年吧)要想学会软件开发,首先要学会使用数据库,要学会写sql,因为sql 是操作数据的根本,然后要学会存储过程,因为存储过程是性能优化的重要手段,还要学会视图,因为视图是数据关联的桥梁,还有触发器,是弥补功能缺陷的利器。而现在呢?这些全不被提倡了,这些传统的技术全成为了ORM 的绊脚石。在ORM框架里,数据库的组织和处理能力全部被实体类代替了,只剩下存储这一个功能了。
现在的程序员不了解数据库,软件系统出了问题也不知道是什么原因,代码没问题,问题可能出在数据上。就好比现在的程序员也不懂硬件,甚至不会安装操作系统。我们那时候的程序员在很多人眼里就是修电脑的,其实主业是写代码,但人家不关心,也不了解,只知道会修电脑。现在呢,不是这样了,有人说这是行业的细分,专业的人就得做专业的事,但我觉得有些相关技能还是自己会比较好。
程序员必须要懂数据库,这是根本。
我遇到过一个程序员,他说他从来不会去写sql,用ORM框架多简单,不用ORM 就是一种落后,他很执着,我是没法反驳他的,因为这是当下流行的趋势,我只知道,要解决软件系统的问题,只了解程序代码是不够的,很多深层次的问题要查数据库,分析数据,这才能从本质上解决问题,否则很可能改了这个问题出来那个问题,按下葫芦起来瓢,永远改不完的问题。
喜欢使用ORM框架的我总结了两个原因,第一、为了跨数据库,一套代码适合各种数据库;第二、不用学习sql语法、视图、存储过程、触发器,省去学习数据库的成本,其他我想不出更合适的理由。
我不喜欢这种开发模式,ORM 把本该放在数据库上的精力,放在了程序上,那些拉姆达表达式比sql还难理解,我觉得这是本末倒置,认知上的倒退,道在迩而求诸远,事在易而求诸难。其实每种开发方式都有它的优缺点,没有对错,只有是否合适,适合你的,对你来说就是对的,不适合你的对你来说就是错的,不能一概而论。要解决问题,就用自己最擅长的方式。
我提倡的方式是不用实体类,用传统的sql和DataTable,这是最简单最灵活的方式,也就是最传统的方式。这样既能熟悉了数据库又能灵活的编写代码。
我知道我写这篇文章很多人会反对,因为现在估计80%的人都在使用ORM框架开发,包括以前喜欢写sql的那批老程序员。这里呢我也不想争论,能解决问题就行。愿意用啥就用啥,但是要做到问题到我这里结束,不要说我不会这个,这个不管我的事,数据库我不会查,每个人要对自己选择的方式负责到底。
几年前,我比较看中程序代码的编写规范,我觉得代码应该看上去比较舒服,编码风格要一致,不仅仅是实现功能。有一位资深的程序员,不太认同,他说不管代码写的怎样,只要数据对就行,以数据库为准,现在我觉得他说的有一定道理,每个程序员的水平不一样,程序写的不好可以重写,但数据要对。产生数据的方式有很多,数据来源不止一个,有可能是手工录入的,有可能是接口推送的,有可能是导入的,不管是那种方式都要确保数据的准确性和完整性。
所以从本质上来说,数据访问层使用什么方式并不是最重要的。数据准确性和完整性才是最重要的。
如果你赞同上面的分析,那么继续向下看如何设计简单实用的数据访问层,如果不赞同那就到次为止。因为后面的设计思想可能让你更加不屑。
数据访问层的设计要解决一下几个问题:
1、连接数据库。要支持连接多种类型的数据库,方式主要是通过官方提供的数据库访问类,例如SqlDataClient。
2、基本的数据访问方法。执行新建insert、更新update、查询select,以及调用存储过程,这些基本就够了。再进一步归纳一下就是两个方法,ExecuteNonQuery 和ExecuteQuery。
3、执行数据操作返回的数据对象。新增和更新返回的是影响的数据行数,存储过程返回的是执行是否成功(尽量避免使用存储过程的返回值),这些没什么好说的。需要说的是查询数据时返回数据对象。有两种对象,一种是DataTable ,一种是DataReader,DataTable 是比较传统的方式,也是最早被广泛使用的。DataReader 是后期才有的,可以看作是DataTable 的只读形式,目的是提高读取的性能,ORM 框架就是把DataReader 映射成实体类。这是目前被提倡的开发模式,但我们不用这种方式,而是继续用DataTable,因为我们不使用实体类。
下面我们对这三个问题展开说明,具体如何实现。
第一个问题,如何实现连接多种类型的数据库。
如在访问SqlServer 数据库的时候使用SqlDataClient(Net版本不同名字可能不一样),访问MySql 数据库的时候使用MySql.DataClient。 最简单的办法就是使用接口类,定义一个接口类,把所有数据访问方法都定义出来,使用接口来调用方法,再创建访问SqlServer 的实现类和MySql 的实现类,都要实现该接口。然后最外层再做一个代理类Agent ,用来确定接口要调用哪个类。
程序结构如下图:

接口的定义如下图(只显示主要的方法):

代理类的定义如下图(只显示主要方法):

第二个问题,操作数据库的基本方法,有人说基本方法是增删改查,这个没错,我觉得再进一步会归纳一下是执行和获取数据。就是两个方法ExecuteNonQuery和ExecuteQuery。其他所有的操作都是围绕这两个方法,也可以说都是调用这两个方法。要在每种类型的数据库访问类中实现这几个基本方法。
以SqlServer 为例列出基本方法如下图(只显示主要的方法):

第三个问题,返回数据的对象。
前面说了我们用DataTable,而不用数据实体,主要是因为它灵活。这样设计是在项目中成本最低的方案。我曾经设想过使用数据实体的场景,那就是业务需求非常明确,调研充分,数据结构基本定型,字段数量类型都很确切,至少业务模型预演了几遍的,也就保证功能做出来不会经常修改。这种情况我见过,做过对日外包项目的都知道,简直是变态的设计要求,一个字段,一个输入框的尺寸,字数限制,验证提示都要在文档里写清楚,项目周期三个月的话文档要写两个半月。现实中这种情况太少了,很多情况我们拿到个大概的需求就动手了,一边做一边完善数据库字段,大方向不会错,增加个字段是常有的事。不要说这是违反软件开发规范的,严格遵守软件规范是很理想的事情,不知道大厂们是否能严格遵守软件开发规范?
ExecuteQuery包括两个方法,一个是返回DataTable,一个是返回DataReader,严格来讲DataTable并不存在,在Net的Framework或NetCore框架中有两种方式返回DataTable,一种是通过DataAdapter对象的Fill方法填充DataTable,一种是使用DataReader填充DataTable,其实这已经足够用了。


两种方法稍微有点差异,以后再慢慢说,真正使用的时候就知道了,这里不能说多了,说多容易跑题。
数据库访问类的设计是重点,除了提供基本方法外,还要处理好数据库连接、数据库关闭,开启事务、关闭事务,事务回滚。这都是很重要的概念,如果数据库只开不关,很快连接数量就用完了。初学者很容易犯的错误,一旦出现还很难排查。再有一个重要的概念是同一批数据操作尽量在一个数据库开闭中完成,不要反复的开闭数据库,例如,修改一条商品信息,先查询商品编号是否已经存在,如果不存在可以保存,已经存在提示重复,不能保存。这个功能包括2个数据操作,一个是按编号查询商品表,一个是更新商品表。这两个动作要在一个数据库连接中完成。不能每个动作都要开闭数据库。在早期的数据库访问层HFBPM3.5中,存在这个问题。那时候的设计考虑要支持本地调用和远程调用(WCF的方式),每个数据操作都可以调用远程的方法,不能在一个本地连接里完成。后来的版本去掉了远程调用。
提到WCF不得不跑个题,因为涉及到微软的技术框架,很早微软推出了三大框架WCF、WFF、WPF,至今只留下了WPF,其他两个在微软Core中基本消失了。微软的东西是好,用起来方便,功能也强大,但淘汰也快,太理想化的东西容易走偏,有些东西出的晚,生态就不好。比如这个MVC的架构,我到现在没怎么使用这个架构,但是这个思想我一直在用,在微软asp.net的时候,web界面要使用服务端控件,这个不方便,很快就被淘汰了,随着ajax和jquery的兴起时,我们在传统的aspx上使用ajax和jquery,即使用了aspx的后台功能,又使用了js前端框架的优势,就没有使用MVC,说实话微软的MVC框架出来的有点晚,我们已经在做了,HFbpm4.0使用的就是这种架构,这种架构也类似于NetCore的WebAPI架构,后台接口使用的MVC的路由机制,利用反射技术动态调用方法,比WebAPI还要灵活,WebAPI的依赖注入也有很多不灵活的地方,这里要细讲也需要很大的篇幅,先不赘述了,有兴趣的可以看一下hfbpm4.0的程序架构,总之一句话,用最简单的技术实现最基本的功能,大道至简。
这个题跑的有点远,赶紧回到正题上,开闭数据库和数据库事务,这些功能要手动来完成,默认的情况下,每次数据操作都要开闭数据库一次。我们通过数学控制数据操作后下不要关闭数据库连接,等操作完后再手动关闭。如下面的代码:

手动开启事务,手动关闭事务,如下图:

这些测试工作是比较复杂的,为了方便测试开发了一个测试工具,主要测试多个操作是否开闭了一次数据库,数据库事务是否能正常回滚等。在执行数据库操作时为每次数据库连接定义一个随机数,从数据库连接打开到关闭,所有的数据库操作都返回这个随机数,如果随机数不一样,说明不是在一个数据库连接中完成的操作。如下图:


上面介绍的这些功能,是数据库操作的基本功能,有了这些就可以完成数据库的增删改查了。但是如果对一个开发平台来说,这些还不够,一个开发平台,还需要有数据库维护的功能,即元数据操作的功能,比如创建表、创建字段、修改字段,获取表结构等。这些功能也要在数据库访问层中实现。一个完整的数据库访问层应该包括一下功能:

以上是对数据访问层的一点浅浅的认识,不正确的地方欢迎批评指正,很期待与同行们的交流。淌过的水,走过的路,踩过的坑分享出来,共同提高进步!

浙公网安备 33010602011771号