架构陷阱:不要任何事情都使用 ORM 实体


在用JPA/Hibernate设计应用程序时,我遇到了一种常见的模式,即建议开发者通过持久化实体和ORM来尽可能多地引导他们与数据库的交互,不惜一切代价避免编写SQL。

这似乎主要是源于一种信念,即这种方法将最大限度地提高模型的灵活性和数据库的可移植性。有时,这也是对学习SQL的一种厌恶。

移植性的论点有一定的道理,但在实践中,如此明显地限制自己通常是一个错误的决定,原因有几个,我将在这篇文章中概述。


你是否真的需要在根本不同的数据库技术之间进行转换?

一个更常见的现实世界的要求是:在不同的SQL数据库和版本之间的可移植性--特别是如果你是一个有多个客户的应用程序供应商,他们可能喜欢使用不同的数据库。

作为一个旁观者,良好的架构实践(如使用六边形架构)将使你有可能在未来重构到新的存储技术而不破坏公共接口,这几乎肯定是比试图实现将实现代码从其底层持久性技术中完全抽象出来的西西弗任务更好的方法。

不要逃避SQL,要拥抱它!

我怀疑在Web应用程序开发中,有一个最大的谎言是,如果你使用ORM,你就可以避免编写和理解SQL,"它只是一个实现细节"。这在一开始可能是真的,但一旦你超越了基础知识,就会很快消失。

举个例子,我经常看到findAll加过滤器的反模式。这是指你从一个集合中获取所有的记录,然后用你的应用程序来执行一些简单的过滤来包括/排除记录。

让数据库来做这种过滤会更好。毕竟,这是所有从事数据库工作的聪明人花费大量时间和精力来优化的。

对于大多数ORM来说,你可以选择编写与SQL类似的东西,这可以让你走得更远。例如,JPA有JPQL,Hibernate有HQL。这些可以让你建立抽象的查询,应该可以在你的ORM支持的所有数据库上工作。

这意味着你的团队需要接受SQL并了解如何使用它,而不是通过使用应用代码来避免它。

为了消除人们在这方面的焦虑:你不需要成为一个SQL大师,就可以开始熟悉你的绝大多数实施要求所需要的东西。也有很好的资源和书籍,我将在下面链接一些。

与过去一些夸张的说法相反,SQL并没有消失,也不只是一种遗留技术。基本的SQL知识是对你职业生涯的一个很好的投资,你会发现它在所有地方都很有用。


避开本地SQL的陷阱
像JPQL这样的ORM SQL类似物只能让你走到这里,一旦你开始做更复杂的事情,你会发现你已经超出了可能的范围(你的里程会有所不同,这取决于你使用的ORM和版本)。

例如,如果你想创建物化视图、触发器、更复杂的查询结构等,你通常不会在你的ORM中找到这些。

对此,大致有两种合理的解决方案。

1)编写严格符合标准的本地SQL。这对于较简单的情况来说,可以合理地工作,但是许多数据库并不完全符合SQL标准,所以你会得到一些不受欢迎的意外,需要重构以找到最低的共同点。

2)使用像jOOQ这样的流畅的SQL生成器来代表你处理 "我应该为这个语句生成什么SQL "的问题。这可以让你以你喜欢的方式写你的查询,而jOOQ将为你所针对的数据库和版本找出正确的SQL语句。其他语言也有类似的功能。

到今天为止,我想说的是,如果你能朝这个方向走,后一种方法会更好--如果你需要支持多个数据库,卡住维护本地SQL会很麻烦。如果可能的话,让一个像jOOQ这样的库来为你处理吧

程序化查询构建
这是Java特有的,但我强烈建议避免使用JPA的标准API。按照现代的标准,标准构建器的设计是很复杂的,人们发现理解和维护代码非常困难。

值得庆幸的是,现在有一些很好的替代品,不会让你感到头疼。同样,我个人可以推荐的两个解决方案是:jOOQ和BlazePersistence。

两者都能让你访问一个现代的、流畅的API,让你以编程方式建立查询,而不会有麻烦。

BlazePersistence是以Hibernate为中心的,但对所有数据库都是免费的,而jOOQ有更广泛的功能,但对某些不免费的企业数据库来说,每个开发者需要支付少量的费用。我认为jOOQ是值得付费的,但你可能要与你公司的会计人员争论以获得批准。如果你使用普通的免费和开源数据库,如Postgres,jOOQ是免费的。


投影是你的朋友
我发现JPA/Hibernate内置的处理投影的方式非常不方便,而且难以维护(例如,基于构造函数的冗长语法)。我怀疑这可能是很多人使用低效的 "应用投影 "方式的原因。

相反,我建议使用BlazePersistence的实体视图模块或jOOQ的DTOs/projections。这两者都提供了更方便的映射方式,通常都有代码生成,以消除让你的Java类与你的投影查询保持 "同步 "的要求。

经验表明,在返回大量数据和复杂的查询时,使用投影projection会很快得到回报,所以不要害羞!

顺便说一句,如果你使用的是较新版本的Java,你也可以利用记录Record来进一步减少模板的噪音。

一个潜在的注意事项是,如果你在你的ORM中使用二级缓存,你可能需要有选择地刷新它以避免呆滞性问题。人们对ORM中的二级缓存有很强的意见,我在这里就不多说了。


不要害怕混合和匹配
将你的ORM用于基本功能(通常是你所需要的很大一部分),当你超出它的能力时,就使用替代方案。不要觉得你需要完全买入一个单一的方法。

但是,这一切都太明显了!

更有经验的开发者经常会说这一切都很明显。然而,我已经无数次地看到这些错误,所以很明显,这值得重复。作为一个行业,我怀疑我们并不善于将这些主题的制度性知识传给年轻的开发者。

另外,在软件工程中,我们喜欢抛弃正统的东西,直到我们发现我们用老方法做是有原因的。例如,从2012年开始的 "SQL已死 "的热潮,然后是SQL的复兴,当时我们意识到它在很多方面都很好(并纳入了NoSQL存储的一些想法,如JSON支持)。