面向对象的数据库开发--再论ORM
ORM不是一个新的概念,已经有一定的历史了。当今流行的主流ORM框架也不在少数,各种轻量级的类库更是不计其数。相关的文章也不再少数,所以算上本文就算是再论吧。之所以写这篇博文,其原因是我自己实现了一个ORM框架,并且融入了很多自己的想法。所以本文想从面向对象的数据库开发这个角度来探讨一下我对ORM的理解和定位。这是一个与ORM紧密相关却又明显有区别的话题。本文不讨论何为面向对象,面向对象的好处,以及为什么要用面向对象的方法来开发数据库相关的应用。这里就简单的假设,一定要用面向对象的方法来开发,以此为基础来讨论ORM。

先说一下ORM是干什么用的。根据维基百科的解释是,ORM完成数据和面向对象编程语言中的不兼容的类型系统之间的转换。从目前我看到的ORM来看,确实是按照这个概念定义来做的。但是我对ORM的理解不止于此,这个在下文再做解释。先谈一下我对目前ORM最被指责的几个问题的看法。这些话早就想说,今天算是终于实现了。

1.性能差
这点可能在所有讨论ORM缺点的文章中都是位居第一的。ORM的性能瓶颈一般来说是在于将数据实例化为对象的过程中,当然还有其它的一些开销。但是问题是ORM得到的是对象,而传统的方法得到的是数据集,两者的结果是不一样的,所以实际上是没有可比性的。这取决于,开发者需要数据还是需要对象。如果需要对象,那么不用ORM的话开发者就要自己做,这样也会增加性能上的开销。其次,ORM的性能慢多少?避开ORM低水平实现的情况不说,实际上将操作的数据量限制在一个比较实际的范围内时,ORM的性能低不了多少,或者说在实际开发中是完全可以接受和满足要求的。所以,这个问题的本质在于,ORM需要提供两种方式操作数据,一种是面向对象的,另一种是面向数据的,从而给开发者最大的灵活性,而性能问题不是本质问题。那么为什么需要ORM来提供两种接口的支持呢?为什么不是程序员自己来做呢?需要对象时用ORM,需要数据时直接用常规方法。这个涉及到我对ORM的理解,下文会有解释。

2.访问数据的灵活性
如果一个ORM支持的4个基本操作是基于sql语句文本的话,那么开发的体验会差很多,让人觉得还是在直接写sql语句操作数据库。如果ORM提供特定的方法来支持4个基本操作,那么如何完成复杂的查询,以及如何输入复杂的条件表达式就是一个挑战。我承认这的确是ORM的一个问题,而且目前绝大多数的ORM框架(包括我当时见到的Hibernate,可能现在不是这样了)也是如此。为此我在实现EMLib这个ORM框架时同时实现了Eql,这个类似于Linq的技术成功的解决了这个问题。Eql可以不用字符串拼接,完成任意复杂度的Sql语句以及任意复杂度的条件输入。

3.数据量的访问不同
这个意思是,更新或者读取数据时,传统的方式可以只读取或者更新需要的字段中的数据,但是ORM是更新或者读取全部字段的数据。这也是一个问题,尤其在字段中的数据比较大的时候,比如某一个字段包含的是一个图片。我认为本质上,这个问题是观念之争,也就是开发者需要面向对象还是面向数据。如果是前者那么这些开销是必须承受的。世上没有完美的方案,在获得面向对象的好处时,适当的额外付出一些代价是可以接受的。但是话要说回来,如果这些额外的数据量的访问大到引起性能问题或者不可承受时怎么办?这时作为一个问题,ORM框架的开发者还是需要拿出解决问题的办法来。我觉得可以有两种方法解决:

a.当只操作部分数据时,就是使用ORM框架中面向数据的方法来操作数据。 实际上,当只要操作部分数据时,尤其在查询操作时,获得的结果就不能被看成对象了。所以还不如就直接用面向数据的方式来操作更为贴切。这是我认为ORM框架需要支持面向数据方法的原因之一。

b.通过懒加载来解决。 首先将数据量大的字段设计成一个实体,将该字段对应的实体成员的类型指定为那个实体。而在ORM操作中使用懒加载访问数据,大数据字段不到实际使用时不读取,这样就可以很好的解决读取数据时数据量访问过大的问题了。

对于更新操作,我没有看到其他ORM框架给出很好的解决办法。我在EMLib中的解决办法是使用Eql语句对象直接执行更新操作,同时Eql支持保持内存和数据库数据一致的全局事务,从而解决更新时访问数据量偏大的问题。

4.数据同步问题
这个问题,我只见过一个人提出来,其它地方都没有见到。这个问题给我很深刻的印象,也是我开发Eql的最初动因。这个问题是,如果直接执行一个sql语句修改了数据库中的数据,那么内存中对应实体的成员的值是不会被更改的。这样内存中数据和数据库中的数据就不一致了。从映射角度来说,确实不完美。但是直接执行sql操作数据已经超出了ORM可控制的范围,所以ORM无能为力也是情有可原的。但是作为一个问题,该如何解决呢?我的方案是Eql和全局事务管理技术。使用Eql技术可以让ORM执行各种sql语句对象,其能力与直接执行sql字符串是等价的。而全局事务管理技术,可以让ORM的事务和数据库中的事务成为一个整体,从而保证内存中的数据和数据库中的数据是一致的。所以在我的ORM框架中成功的解决了这个问题。

下面谈一下我理解的ORM应该是一个什么样子。我觉得按照维基百科上的说法,ORM的定位过于简单了,应该换一个名字,比如:面向对象的数据库开发库,之类的名字。所以我做的ORM框架的名字是EMLib,全称是Entity Model Lib。这样的定位要求ORM(暂且还是用这个名字)框架提供一个类库,该类库具备完备的面向对象的数据库操作能力。而不只是提供若干实用的方法就完了。其次,这个类库还要提供其他一些能力,方便程序员操作数据库中的数据。这些便利虽然不是必须的,但是却能够显著的提高开发效率,增强编码体验。再其次一点就是能够独立承担并完成数据层的职责。下面具体谈谈这三点

1.完备的面向对象的数据库开发能力
目前使用最多的还是关系型数据库,所以数据库中的所有数据操作,都可以归结为几个基本的集合操作,即:补交并差积。所以对基本操作而言,完成增删改查就够了。但是需要增强的有以下两点:

a.可以执行任何复杂度的sql语句,可以构造任意复杂度的条件表达式。
b.可以执行数据库的库函数。

当然前提是不能用字符串拼接的方式(为什么不能用拼接方式在第三点说明)。如果做到这两点,那么基本上就可以完成对数据库的任意操作了,也达到所谓的完备性了。

2.提供足够的能力,提升开发效率
这是一个重点,也是对完备性的一个重要扩展和增强。这里列举几个功能点讨论一下。

a.级联操作
我个人认为这个功能几乎就是必须的,其原因不仅仅是方便的问题。因为实体模型在设计时,在实体与实体之间会必然的存在继承关系,这样就会导致多态的出现。如果ORM不直接支持级联操作,这个过程需要程序员自己完成的话,那么Sql语句的书写以及编写完成级联操作的代码的过程是比较繁琐而重复的。并且由于多态的情况是由模型自身确定的,所以在源代码级很难做到,继承关系变化而操作级联的代码可以不变。所以一个ORM框架支持级联操作基本上可以认为是必须的。当然考虑到性能的问题开发者可以不用级联这种功能,但是ORM要有这个能力。对于EMLib来说在这方面做的更完美,不但支持级联,出于性能和灵活性方面的考虑,还支持可以控制深度的级联操作。

b.实体类型成员
这个功能支持是完整体支持面向对象设计的体现。没有这个功能面向对象就不完整了。

c.关系操作
应该说级联本身就是利用关系操作的一种体现。但是关系的两个基本操作需要独立的支持,这两个操作就是关系的建立和终止。如果不支持会如何?那么开发者必须自己编写代码修改外键,然后再修改内存中的数据。当关系关联的双方实体涉及多态情况时,就会比较繁琐,模型变化时,代码修改就会相对复杂起来。所以ORM也应该支持这两个操作。对于 EMLib来说,还支持第三种操作,那就是关系的转移。转移的意思是终止与一个实体的关系,然后与另一个实体建立关系。所有三种操作EMLib支持以实体对象为参数的操作,和以Eql语句对象为参数的操作。

d.懒加载
这个功能的迫切性不是特别强,但是能实现的话还是实现为好。

e.实体的唯一性
这也就是实体的唯一引用问题。也就是说需要一个缓存,保存所有已经映射出来的实体,如果再次映射就直接从缓存中获取,而不是再次新创建一个。在我看到的文章中,提到这一点时都是从性能角度来说的。因为使用了缓存可以减少实例化实体的时间开销。但是我的理解是,这是面向对象自身的要求,和性能无关,是一个必须支持的功能。因为任何面向对象的编程语言在处理对象时都是用引用类型来处理的。也就是说同一个对象永远只有一个。况且,从映射的角度来说,同一个记录也只能映射同一个对象。

3.独立承担数据层的职责
目前所见到的所有ORM框架,定位在为实现数据层提供支持的地位,而不是自身就已经独立承担了数据层的责任。那么独立承担数据层的责任的理由是什么呢?理由很简单,如果数据层还需要自己做的话那我就不需要一个所谓独立的ORM框架了。这个要求对于基于维基百科对ORM的定位来说,是超出了ORM的概念的。那么需要满足什么样的条件才能独立承担数据层的责任呢?

首先,ORM框架的功能要足够强,足够灵活,封装数据操作细节。强就不用说了,这里所说的灵活就是ORM框架需要支持面向数据和面向数据两套操作方式。不可能想象要求开发者操作数据库时使用两个类库来操作。一个是ORM,当ORM不灵活时,再用开发工具提供的类库以面向数据的方式完成数据操作。其次,类库的方法设计恰当,这可以保证ORM框架提供的接口使得开发者可以从实现业务功能层面考虑问题,而不需要在数据如果操作这个层面上来考虑问题。我个人认为这里面一个关键步骤就是类似EMLib框架实现的Eql技术的存在。让我们来讨论一个具体的例子。

假设我们输入员工的工号,然后查看该员工的信息,我们把这个称为员工查询,并且简化到只能输入员工工号来查询员工。考虑普通ORM框架的作法:
1.获得输入的工号。
2.构造Sql语句的文本。
3.以该文本为参数,执行相关方法获得对应的员工对象。

EMLib框架的作法:
1.获得输入的工号。
2.构造Eql查询语句对象。
3.以该语句对象为参数,执行相关方法获得对应的员工对象。

这里的差异就在第二步,其中表现出来的具体差异在于: 拼接Sql文本属于数据层的内容,在实现业务功能时直接出现了数据层东西。这说明使用到的ORM没用阻止开发者直接进入到数据层,也就是说ORM没有独立承担数据层的责任。而构造Eql语句对象却不是数据层的内容。理由如下:

1.从内容上来说,Eql属于EMLib框架的一部分,开发者使用的是EMLib,没有直接涉及到数据层。
2.构造Eql语句对象是使用方法调用方式完成的,构造的是一个语句对象,不是直接处理数据层的内容。
3.从结构上来说,Eql本质上是避免开发者直接使用Sql语句文本,其位置在开发者和Sql语句文本之间,从而阻隔了开发者直接进入数据层。

最典型的体现在于同一个Eql语句对象可以在不同的数据库上直接执行,而无需修改。其内容的处理有EMLib自动完成。这个简单的例子可能暴露出的问题还不是很明显,读者可以体会一下。但是如果在所有的操作中都存在这样的差异,并且这种差异还会更大,那么ORM框架就可以独立承担起数据层的责任了。
Copyright 2014--2020 广联科技版权所有