正文

我需要什么样的ORM(对象持久层)2006-07-24 14:32:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/Csharpsky/16790.html

分享到:

首先声明,标题中“我”,不“我们”,别人希望如何我不太能够帮着下结论。

一个可以打动我的持久化层(Persistence Layer)或者说OR/M(Object Relateion Mapping)应该具备哪些特征呢?

1、简单,尽可能地简单
没必要为了我偶尔用到的功能花费太多的精力,CRUD满足之后,关于Relation如何处理的问题,我觉得尽可以不必那么讲究,一定要那么OO吗? 一定要 order.items这样去访问order的明细吗? 我觉得太可不必,为了实现这种功能,一我必须再设计Entity类,以决定有哪些OnetoOne、OnetoMany, ManytoMany的关系,然后会定义一个类似这样的很OO的class出来:
class order
{
.....
customer _customer;
entitylist _items;
}

看上去真的很帅,但我必须去维护它,因为不所有的关系都需要这样定义,lazy loading并不能让人就可以放肆地定义一个很“胖”的类出来。

如果有人说我可以先class,再使用schema工具生成数据库,那么很不幸,我们没有共同语言了,起码我现在对这种方式不感兴趣,而且我也从来不这样做,像如何让union all构造出来的view利用到索引里所说的一样,我会因为数据库增加一些不相关的列,也会因为性能而再增设冗余,用class的方式去思考的话,我很难知道最终的数据库要做多少修改。如果仍然用“表”的方式去先构建“类”,那么我觉得这一种掩耳盗铃的行为。顺便说一句,我暂时对domain object还不很感兴趣。entity就用来存储数据的,其它的事千万别让它去承担。

那么我希望如何处理relation呢? 非常简单:
entitylist items = pl.getList(order, typeof(orderline));

那么如何生成filter呢? 命名规则+代码生成,用order的主键id的值,以及用orderline的order_id列,构造select ... from orderline where order_id = order.id

2、透明,让我知道所发生的一切

我不希望在保存一个或一组entity时,会发生我并不知道的级联更新,或者有些认为我没有dirty flag而不做任何更新,因为有时候一个update t set f = f 并不什么活都不干的,当我要retrieve时,别因为缓存中已经有了就不从数据库加载了,因为可能我确实需要最新的数据,或者把数据缓存而不提交,这样我的另一个client会因此而无法即时获取数据,数据库的连接也要让我知道,使用了连接池还立即释放掉了,什么时候会开启事务,什么时候会提交事务,这些,我统统要知道。

有人会有意见了,ORM不就为了隔离数据库么,你要知道那么多鸡毛蒜皮的小事干什么?

我只能说,这些东西如果我不知道,那么我就很难写出对数据库优化的处理。而需要这个的话,仅仅可以trace generated sql不够的,我希望当告诉持久层去买烟时,它不要很机灵地顺便买个打火机回来,尽管很多时候这个机灵很讨好的,但有时候可能会因此给我带好不必要的麻烦。

这里透明的意义就可控性,当我要它做的时候再去做。

3、可测试性,减少重构或者维护的工作量

这个重要了,假如说我要构造一个查询,怎么办? criteria.add(a.ID like 'a',LogicOperator.ADD)? 这种方法又似乎很OO了,但当要构造一个
(a and((b or c) and c)这样的条件时,写出来的语法可能连你自己都搞不清楚之间的关系了, 而且如果再加上join,再加上select fileds,会什么样子?相当于原来SQL的语法树了,假如再来一些,计算列,case 判断,group,having,order by, exists... 天啊,受不了,还用VIEW吧,可view还得处理filter的问题,那么,换做Store procedure吧,传递的参数怎么办?用
(@a is null or a = @a)这样么? 再定义那些parameters,这个SP写得就比较难看了,再多来点if else的处理吧,SQL脚本就更长了...

一个查询或者复杂的更新,如何最易于理解的? 就SQL,要用对象化来转述它,只能让人看得云里雾里,ORM要不要去处理这些事情?我觉得要。但最后的实现方式一易于理解,二要可验证否正确,三要易于维护。

用cirteria构造,难于理解,也无法完全验证;用SP,难于维护,当加一个filter时,又要去修改sp对应的CLASS,如果不用sp<->class,那么关于参数的验证又无法保证了,因为可能有个地方你调用了exec sp @a,@b,@c,但后来这个SP可能参数定义,参数位置,参数个数都改变了,原来的调用就隐藏了一个BUG了,如果测试做得足够好的话当然没话说,但你真能做到100%的覆盖用例?要知道还有大量的if else存在。

那么我现在如何做的呢?很简单,将SQL定义和代码分离,你放哪都行,怎么加密随便你,SQL的写法这样的:
select a,b,c from t where #x# and #y# or z = :z

#x#一个filter,当没有fitler定义给它时,它会用1=1替换,:z参数。
这样有什么好处? 首先这条语句可检验,当我用xml同样定义了filter后,我可以将对应的filter替换,并用相应的值替换:z,然后我就得到一个完整的SQL语句,这个SQL可以提交到数据库去检验的。那么如何设置参数的实际值呢? 还通过命名规则来映射,这个值会来自我程序内部维护的一个data store。

4、性能,瓶颈不能发生在这个地方

虽然说数据库执行SQL的时间会远大于ORM层处理和生成SQL的时间,但我的要求就生成的SQL千万不要和手写SQL相差太多,对于批量的操作,一定要尽可能地快,有些批量的更新要给我一种特殊的操作,比如说对order的所有items设置一个flag,就没必要让我去遍历list,再一个个设置,再提交了,让我在一行代码写完这个处理,并且生成update orderline set flag = 1 where orderid = @id这样的SQL。

而这点和第2点有点重合了,只有当可控性强了之后,才有可能去避免性能问题。

那么对于集合操作呢? 我永远不需要items[3]这样去访问第3条数据,我永远不会加载一批数据,却只为了修改其中的几个,也就说order.items对我来说没有意义,我会如何操纵这些集合数据呢?

大概这样的:
C(create):
list = new list();
list.add(item);
pl.addUpdatetoQueue(order);
pl.addUpdatetoQueue(list);
pl.commit;

R(Retrieve):
list = pl.getList(order, typeof(orderline)

U(Update):
list["ABCDE"].price = 1;// 主键为abcde的orderline

D(Delete):
pl.delete(order, typeof(orderline));// 如果有级联删除就不用了
pl.delete(order);

这里持久层很像一个Util或者Helper了吧? OO的味道似乎完全没有了,但,这就我想要的东西。

阅读(5016) | 评论(2)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册