我和ORM的故事

在现代程序开发的过程中,ORM无疑是我们的重要开发工具,ORM的诞生大大解放了生产力。每种语言都会有N套ORM框架,有轻量的、重量的、讲究灵活的、追求性能的…不胜枚举。但每样技术都有两面性,ORM同样如此。各个社区对ORM的争吵基本成了月经话题。大有“彼之蜜糖,吾之砒霜”之意。ORM同样经历了各式各样的变革。我是ORM的坚定支持者,我也曾被ORM狠狠虐过,也算经历过几代ORM的变迁,自己也开发过不止一套ORM,个中酸甜苦辣不一而足。今天,和大家聊聊我和ORM的故事。

一、上古时代

我刚刚工作的时候,大概是代码生成器时代最后的余晖。那时候各种包依赖还靠ctrl+cctrl+v,哪个老司机手里有一套自己做的生成数据库各种操作的代码生成器,那可牛逼坏了…
orm-in-my-opinion-201865203257

那时候代码生成器还是可以卖的。通常,这样的代码生成器会根据你指定的表生成对应的实体模型类,再生成一系列的常用操作类,增删改查;高级一点的,会生成一些自定义的逻辑关系处理类,你可以通过这些类描述你的查询逻辑,从而进行单表操作,那时候是C#的partial关键字最风光的时候了吧。这样的玩法其实今天依旧大量存在,例如Mybatis就是一个典型,只是那时候一切显得比较原始,Mapper操作还得靠一堆reader[‘xx’]来实现,通常代码生成器是码农的核心资产,你要业务代码没问题,只是这个代码读起来容易让人感到胃部不适,毕竟,通常没人去招惹这些机器写的代码。那个时代,SQLHelper还在横行天下,各位老司机还在想方设法让代码参数化能写起来更简单一些,尽最大努力避免程序员拼接参数造成系统漏洞…拼接参数是那么诱人,比参数化写法不知简单到哪里去了。

那个年代,大家都在吃糠咽菜,作为新人的我,一点点学着了解项目结构,在不知道代码生成器这货的情况下不小心进入了其生成的神迹~几千行代码参差不齐地放在眼前,SQL语句和程序代码堆叠于屏幕。我掉进了代码的汪洋里,感觉自己就快被淹死了。还有时候,弱鸡的我修改/删除了表里的一个字段,也尽量地用ctrl+F查找被修改的相关内容,可还是会在上线了几分钟后在某个不起眼的角落里,爆出了黄页…还有无数个傍晚,一双油腻的抓着小龙虾的手艰难地接起DBA大人的电话:“你的执行计划怎么又乱飘了?是不是又在拼参数?”那时候的阴影导致了我至今对DBA都俯首帖耳…

二、疯狂时代

orm-in-my-opinion-201865212325
后来,我第一次认识了完整的ORM:Entity Framework它是电,它是光,它是唯一的神话!我刚接触EF时,它还在4.X版本,看见它超低的学习门槛,优雅的操作方式,多表关联也不在话下…最重要的是,安全感!从此妈妈再也不用担心我们改表。任他表结构如何变更,我只需重新生成一下实体类型。任他改变了字段名、字段类型、字段顺序… I don’t care! 编译期安全检查,让我操作数据库从未如此饱含安全感。

幸福的日子并没有持续太久…DBA的通缉令很快贴满了办公室:

这是哪个SB写的语句?为什么一个查询语句可以长达上百KB?

这人是不是脑子有问题?为什么要那么多的子查询?

这语句是什么鬼?居然怎么都无法命中索引?

在DBA的眼里,我的罪证简直罄竹难书,你无法想象,在一个有代码洁癖的DBA眼里ORM生成的那一堆堆语句究竟意味着什么。几个月后,一位刚入职的同事和我说,想用EF写一些复杂查询用来做统计数据展示…我一听,差点跪了,如果真的那样做了,估计我们那暴脾气DBA会把Oracle一体机砸他头上吧…

那时候我意识到,完整概念的ORM为了普适、为了概念完整,可是什么事都做得出来的!DBA的通缉令已下,我又舍不得把那些安全漂亮的代码改成一坨坨的SQL丢在我的项目里,只能向BOSS下军令状:一周,一定能解决这些问题!但是,一周内,你要看着DBA,让他别用服务器来砸我~!

别看我们DBA脾气暴,在那一个星期可是尽心尽力帮我解决问题,我每一次修改、测试他都给我出相关报表协助改进,两天后… 我终于放弃了EF for Oracle 那次我真的怀疑Oracle对EF做了针对性劣化,发生了很多匪夷所思的问题。但是我可不会老老实实退回到SQLHelper的原始社会中去“由简入奢易,由奢入俭难…”我爱的不是EF,我爱的是基于Lambda表达式的SQL DSL,它安全、高效、可读性强。那我干嘛不自己造一个用Lambda表达式生成查询的库呢?那时候我已经认识了 Dapper它如此简单高效,我无需为Executor、Mapper再费脑筋,只需要考虑Lambda->SQL就好了。至今我也依旧认为,C#的Lambda表达式、LINQ,是我所接触过最优雅,表达能力最强的SQL DSL。也不得不佩服M$的高瞻远瞩,Lambda表达式不仅仅是匿名函数的语法糖,它是语言中的语言!它可以在运行时生成语法树,你能对语法树进行解析、处理,编译成其他你所想要的东西…后来,当我看到某些语言因为能将函数作为参数传递而号称支持了Lambda表达式特性时,我通常会笑一笑…那时候我大量参阅了老赵的关于 LINQ TO SQL的资料,从此变成了老赵的脑残粉,他的话会一直记在我对技术追求的心中:“别看我爱C#,论JVM你们没几个人能比我熟!”

在写自己的Lambda->SQL库的过程中,我思考了很多。ORM的概念确实很美好,基于这个概念也有无数的实现,如Entity FrameworkHibernate都是极其典型的实现者。但这个模型过于理想,它想将OOP对SQL的阻抗失配问题彻底地解决。实际上,这几乎是不可能的。在如下两个场景里尤其明显:

1、批量操作

2、锁

在OOP的世界里,批量操作,我们会将集合中符合条件的对象一一取出,并针对每个对象做出相应的操作。ORM是这样做的:从数据库中,将符合条件的条目全部取出,在应用里做相应变更操作,再将变更后的数据一一写回数据库里。很多可以一条SQL语句就能搞定的事情变得无比复杂,应用与数据库交互的数据量不知翻了几个数量级。类似批量删除,批量修改状态的操作简直令人智熄…

锁的问题也很严重,并且它很容易诱导程序员犯错…最简单的场景:计数器。我已经无数次看见程序员通过ORM将数据库中的数据取出,在统计属性上进行+1操作,并将结果存回数据库…我想小米的抢购程序如果是这么写的话,雷军同志也不会被称为猴王了吧…不过小米手机可能不太容易像阿里云虚机一样地超卖吧…

还有ORM本身对更新操作的处理,总是让人纠结性能更重要还是功能更重要…

三、纠结时代

经过了上古时代和疯狂时代的洗礼,互联网的势头越来越强劲,世界在发生着变化。很多过去被视为金科玉律的东西正在被打破:关系型数据库的三范式被部分场景抛弃,反范式越来越成为主流;关系型数据库本身都饱受质疑,NoSQL、NewSQL纷纷开始抬头挑战老大哥的权威。数据库的功能在被简化,存储过程、视图等等数据库的特性被抛弃,甚至连Join都被限制和禁止。应用本身越来越强调横向扩展能力,应用层所要承担的工作越来越多,数据库越来越像一个简单的仓库,需要承担的计算处理任务越来越少。
orm-in-my-opinion-20186522312
此时ORM也在发生着转变。数据库所需的功能简单化也带动了ORM功能的简单化。对ORM的功能大家基本形成了一个共识:1、执行部分需要ORM帮助简化;2、结果映射部分全部交给ORM。 此时,一些所谓“轻量级ORM”开始抬头。C#里的Dapper、JAVA里的Mybatis开始大量吞食概念完备的传统ORM的市场。Hibernate都差点被丢进了垃圾堆,还好有JPA帮助续了一命。
细细体会,如今各种语言的ORM变得更加直接粗暴。它们更像一个个运行时SQL代码生成器,不再追求ORM的概念完备,在尽量保证代码优雅的基础上尽力地贴近直接的SQL操作。人工也越来越可以控制ORM生成出来的SQL,虽然,复杂的SQL操作便捷程度已大大不如从前。
我想,这是一个纠结的时代。一方面,我们依旧追求应用层对数据库的便捷优雅操作,希望ORM提供更多功能;另一方面,我们惧怕ORM,正在努力地减少ORM所要承担的工作任务。还好,C#有LINQ,JAVA8也终于有了stream API,它们都能大大帮助应用层实现原本用数据库实现的一些功能。我也Fork并魔改了一个运行时代码SQL生成工具:SQLinq
我也时常思考,ORM的概念究竟存在哪些问题?究竟是应用组织方式的变化导致了它的衰落还是它本身就是一个脱离实际的概念模型?而如今的模型我并不认为是ORM的最终形态,如今的O-M模型在处理一些问题上同样捉襟见肘,如长管线的对象处理问题我依旧认为传统ORM在这方面存在极大优势。

我们还在前行,社会的发展也总是解决了旧的问题冒出新的问题。码农们,让我们继续折腾!

(ps:说到数据库,必须吐槽,JDBC已经严重拖累JAVA的异步编程,JAVA语言本身已经成为JAVA异步编程发展的最大绊脚石!)

我的微信公众号
我的公众号