续:玩O/R Mapping的体验,及由它想到的,胡思乱想,思维跳跃

续:
.......................................................................................................
    如果是采用实体类的方式,在软件中动态创建一个新列,这将是一个最大的恶梦,当然,这并不是不可实现,只是在实现上将要花费不少的周章。比如,你需要继承这个实体类,然后在新类中增加一个新的字段和对应的属性,然后将外问的访问改为基于子类的访问,这样做听起来不错,但问题在于:需要进行重新编译。有时候,这会成为一个不灵活的方案。

         但DataSet本身的弱点在于,太过于完整的关系映射,将对象与关系的矛盾也完整移植到了程序代码之中,开发人员在开发的过程中,不得不对数据访问的过程在OO与Relation两种模式下不得不对进行思想切换,同时,这对系统的分析与设计也造成了一定的影响。比如微软的.NET示例中,Duwamish里就是对DataSet进行二次封装,将数据库中的构架移植到了程序之中。这样造成了构架的复杂度上升,代码的可维护性的也下降了。因为如果要实现完整的映射,首先不得不将数据库中的结构完整地往代码中进行移植,当数据库中的结构发生了改变之时,映射代码也必须进行改变,前面说过DataSet在映射成为OO方面的实现还很稚,这是因为DataSet本身没有承担建立对象模型的任务。
         DataSet由要承担XML形式描述数据类型的,同时必须保证异种数据库接口都要转换成为指定的数据类型的形式,这使得DataSet本身无法承担起建立对象模式的重担,因此,DataSet中采用了比较中庸的方式:对于关系保留,对于类型转换。
        实际上,即使是一般意义上的O/R Mapping组件,对于关系的映射也做得不是很好,DataSet的可以通过可视化XSD的方式来设计内部的DataTable及其Field成员,并进行关系的描述,大大地简化了开发的过程,同时因为它自己的描述特性,使得采用xsd.exe进行代码生成的复杂度大大地降低。
        虽然,对象间的关系也可以通过UML等图形来进行描述,但描述上的冲突,使得图形化OO与图形化的关系映射间产生了一些小的磨擦---在关系范式之中,满足第三范式要求生成的基础表有粒度较细,要遵循O/R Maping的原则,从封装性与清楚性上表述,要就直求映射的类必须与关系元表进行一一地对应,如果这样做,也同时要求了再包装,这是一个非常不好的现象--大大增强了可维护性的难度,更重要的,这样会产生大量的对应数据库的元表类,一旦数据库中的结构进行了重大的调整,无论是什么样的O/R方案(包括DataSet和非DataSet),都会要求将代码中进行大量的改变。
        面对这种情况,一个较可行的方案是全部基于数据库中基于视图与存储过程的进行处理,DBA可以在后台按照自己的意愿对数据库表进行调整与优化,而数据库与前台程序中的接口则通过视图与存储过程来进行,从而将按照数据库的设计初衷将封装层次黑箱化。
        但此处有一个令人郁闷的事情发生:Visual Studio.NET中的数据适配器向导让人郁闷不已。它加入目标表是否是视图的判断,如果是视图,则不会生成相应的Update、Select、Delete代码,然后就需要全靠手写,这使得数据适配器+DataSet的作用实现一个轻便的数据映射方案大打折扣。所以,最终的解决,还是有赖于全部以存储过程来实现---多么令人郁闷啊,本来可以分离前台访问与后台DBA的操作的,被这样一搅,存储过程就成为了唯一的方案。我们可以看到,在Duwamish里,底层所有的数据操纵方式都是基于存储过程的,不知这是否是微软的本义。

         因此,DataSet的方案,实际上是一个残废的O/R方案,并且以DataSet为中心的数据访问的相关工具极不完善,于是就需要一种易于实现的,不依赖于工具的开发模式出现(其实,开发一个可视化并强大的工具的成本也许更低...),java中的O/R Mapping解决方案正好可以满足这个需求--java里的开发一向缺少强大的自动化工具,但也造成了Java中的框架、组件层出不穷,这种自食其力的代码至上的开发方式,却成就了java,java中绝大多数组件与框架,就是为了满足复用性的需要,减轻开发过程中的繁杂。

        基于对象的数据封装,有利于观察视角的清晰性,O/R Mapping本身是为了解决关系型数据库中的关系与OO中的关系的映射,因此,O/R实质是为了满足对数据的对象化封装,使得数据访问层次与其它层次可以更有效的进行分隔。O/R Mapping组件本质上不应该括异种数据库的支持,因为对异种数据库提供一种通用访问的接口是可以理解,但如果提供相同的实现方式,则是不可取的,目前大多数O/R 组件宣称的异种数据库的支持,实际上是一种宣传性质的借口,迁移数据库尽管困难,但迁移数据库的目的一般是因为原来的数据库无法满足需要才会进行迁移,否则的话,迁移的意义何在?如果为了数据库的迁移能力,而牺牲数据库的特性支持,那么这样做又有何意义?

      异种数据库的访问需求应该是一种自上而下的需求,这个职责本就不应由O/R Mapping这种低层组件所负责,为了O/R的通用性,使得种种数据库特性逐渐消失,这样只能使得O/R组件所提供的功能越来越狭窄,在最终在开发中,仍然无法选择O/R组件。实践上,对于我们来说,有对于某种数据库具有针对性的O/R Mapping组件,才是我们大家真正想要的。
      就个人而言,对于Ms Sql Server2000,我需要的是一个能够支持存储过程,能够支持函数(Sql Server独有),能够支持视图,能够支持触发器的O/R组件,使得我可以事件驱动的方式来对数据库进行操作。
      但这一切并不存在。

      O/R Mapping组件与类型化的DataSet的性能理论上谁会更更好,因为类型化的DataSet中,DataSet实际上是一个集合,它的内部的对象包括了DataTable,不过,这不重要,DataTable是引用类型的,DataTable中包含了DataRow,而DataRow本身也是引用类型的,这也不重要,最重要的是:关键在于DataRow的成员ItemArray,这是一个数组,而且是一个由object构成的数组,地球人都知道,值类型在发生数据转换时,如果经常了object的转化,不可避免地要进行box与unbox,这是一个比较影响性能的操作,在大量的数据成员访问时,每一次访问,都要进行一次装箱与拆箱,这是一件非常痛苦的事。但O/R Mapping组件也好不到哪里去,从数据库中获取的数据,一般也是以object的形式获取的,这涉及到ADO.NET的问题,如果你GetInt32,结果数据库却是空值的话,呵呵,异常就出来。所以一般最保险的方式还是用的getValue后,再对该值进行数据类型转换。显然,在这个级别上,无论是DataSet还O/R Mapping,都占不了上风...再关注一下,DataSet中,每条记录要产生一个DataRow对象,而O/R Mapping组件中,每条记录要产生一个映射出来的类的实例。这方面,Mmm...两者也都占不了上风。不过,DataRow明显比一般的实体类的创建更消耗时间,因为它还有一堆实体类并不拥有的属性与访问方法,在这个层次上,我认为DataRow应该比实体类要慢上一些。更重要的是,DataSet对于内存的占用是没有节制的,而O/R Mapping组件中,一般有专门的缓存管理机制,可以大大地提高访问效率,并根据命中率适当回收不常用的数据,尽管这会有一些耗时,但这样的资源综合利用高上一些。

     从此处看来,似乎O/R Mapping比DataSet的性能要好上一些,而且也应该是不二之选。但奇怪的是,测试结果好像却不如想像的这样贴近人意,在实际的应用中,也会感觉,DataSet的性能似乎要好一些。实际上,此处关键在于组件本身的数据访问方式上,对于现在一般的O/R组件来说,需要牺牲数据数据的访问效率来换取异种数据库的兼容性,而.NET本身的DataSet方案中,数据适配器却是因数据库而异的。尽管一般的持久性组件也有Provider,但是,这些Provider是针对访问的转换(主要是针对不同数据库的SQL写法上的转换),非并且是对数据库本身体现的转换。可以说,统一的数据库兼容性是不少O/R 组件性能的最大杀手,更重要的一点是:在这些O/R Mapping面前,不是不用开发者考虑异种数据库特性,而是它本身将各类数据库的特性进行了平庸化!
    
      目的在使用O/R Mapping的组件上,性能仍然是困扰开者的一大问题,除此之外的,还包括了持久化组件的可信度,众所周所的是:大压力下的数据库并发处理,数据性能提升等,都必须依赖于数据库本身的特色机制来进行处理,这一切往往是程序员无法很好地处理的,通常会依赖于专业的DBA人员来对数据库结构进行调整与优化。
       而现今的如流行的Hibernate、Nhibernate,前台应用程序进行了配置之后,如果与数据库中的表不相对应(比如多出一个类),那么它就会进行“自作聪明”地去做上一些事情(比如创建一张表,或抛出一个异常),当然,这个功能是可以关闭的。但可惜的,几乎目前所有的O/R Mapping组件都有大量的自称有特色的功能,不能不说是有画蛇添足之嫌。

       不少人为了OO而OO,使用O/R Mapping组件,也是为了让自己的程序看起来很OO。
       但啥是OO?为什么要OO?在维基百科全书,如此定义:"面向对象方法是一个广泛使用但涵义并不清晰的术语",这意味着面向对象这四个字本身就是模糊不清的。    
        这世界上的OO有两种:一种是基于类的,一种是基于对象的。将SQL语句用类或原型进行包装一遍是必要的,但不是不是目标。
    对象模型是一个基于三维投影的集合,如果再考虑对象间的变化与动态关系的话,那么整个模型是一个四维的模型(简化起见,我们视对象模型为静态的三维)。而关系模式是一个基于两维平面的映射与投影---这就是将二维与三维间如何相互映射的问题。
      假设一幢立体结构的房子,映射到二维平面上,将会是一个什么样的情况呢?可以想想太阳斜射下来的留下的影子。
      关键就在于此处,三维是很容易在二维上投影的,但这中间需要损失大量的信息,因为你很难从影子中将一个三维结构的房间还原,除非进行大量地多角度投影,然后再分析投影,配合足够的想象能力才有可以还原房子的外部结构。更复杂的是,如果要映射内部结构,除非允许在房间内部进行多角度投影...
      从三维映射到二维后,可以想象,还原信息是一件非常痛苦的事。
      包括我们的编程语言,现在也是2D的语言,它只能够描述一个平面上发生的事情,如果要描述立体结构,必须一个平面一个平面的进行。去年热闹的AOP,也只是对已经描述的多个平面,进行横切,从而达到另一角度切入描述的目的。
      很显然,由于编程语言与关系模式本身的制约,使得我们在描述一个事物的时候,常常不得不去借助一些特殊的手段去实现,更重要的是:因为某些人类能力上的限制,有一些简洁而又方便的手法,不能采用在我们的语言之中,或者说是不被推荐,比如很有争议性的goto。

        OO提供了信息隐藏的机制,OO不在于你使用什么来实现,而是在于什么才是OO的思想,OO更多是一种观念性质的东西,不使用面向对象语言照样可以使用面向对象的思想,反过来,不懂面对象思想,也照样可以用面向对象的语言工具,好的工具只是让你写起来更方便一些,仅此而已。
       著名的例子是1977年,美国海军研究实验室的“海军降低软件成本”的项目(也就是著名的A-7项目),该项目虽然最终商业上是失败的,但是,在研究上是成功的。它遗留下了大量的设计方案、代码、方法及原则,该项目资料几乎是每一个构架师都需要进行接触的。该项目建立了需求分析的文档的典范,并且,整个系统采用OO的设计方法,更有趣的是,在那时,他们就已经开始采用了今天所说的领域模型。
      该项目最大的意义在于它的结论:信处隐藏原则,是一个应该公共遵循的原则,认真研究各模块间的接口与接口描述可以避免集成时产生错误。

       现代O/R Mapping组件大多是为我们解决的就是对数据库访问的便利性,它没有为我们解决根本的问题,比如:灵活的可定义表,字段动态添加删除之类的持久化特性也没有提供(注意“动态”,如果不知道什么是动态的话,请勿评论本贴),目前的O/R Mapping工具还非常死板---你的持久化并不随心所欲。

    比如,我需要持久化一个Windows Form,需要持久其中所有控件的所有属性,包括控件位置等(很像游戏中的:保存进度功能)。我们需要的是一个这样的持久性组件,可以有选择性的或完全地持久化我们一切的属性,在意义上,不仅是要保证“可持久”,还需要“可恢复”的能力。这样的组件,才能够真正满足我们的大众的需要。
    就以Windows Form来说,Visual Stuido.net中的Form Design下,就是一个实现“持久化”与“可恢复”的良好例子,你可以任意拖放控件到指定的位置,当保存后,下一次再打开时,会发现,一切保持与关闭前一样的样子。
    类似常见的例子还有很多,我们往往需要保存数据时,也是需要的类似的能力,而并非仅仅是简单地封装CURD就算完了。虽然目前我们可以使用一些组件对已存在的对象进行持久化,但效果往往并不理想,这是因为组件中缺少对对象中的对象成员进行递归持久化的缘故。

         真正好用的O/R组件,需要抛弃掉所谓的异构数据库的支持(不要得意洋洋的宣称你支持多种数据库,这样的意义并不大),这样才能够做出具有每种数据库的自己特色来,从而发挥各类数据库本身的优势,如微软的ObjectSpace,极有可能会提供对Sql Server更好的支持,甚至仅支持Sql Server---这种做法很正常,不要因此而骂微软霸权,因为Oralce也有专门的O/R Mapping
组件,如TopLink。

感冒发烧中...不写了....越写越胡涂

posted @ 2005-06-07 13:29  一根神棍研古今  阅读(3696)  评论(8编辑  收藏  举报
Web Counter