J2EE持久化策略考察——为项目做技术选型准备的一点资料
背景
O/R映射技术的出场顺序:
1997年-1998年:TopLink,CocoBase,ODMG
1999年-2001年:Entity Bean,JDO
2002年-2003年:TopLink,Hibernate,iBatis数据库层
2004年:JDO稳步发展;Hibernate飞黄腾达
功能
以下出自Rod Johnson《J2EE设计开发编程指南》
使用EJB2.0实体进行O/R建模的限制:
尽管引进了各种重要增强,但根据制定,CMP实体组件依然是O/R映射的一个基本形式。EJB规范忽略了O/R映射方面最难办的部分问题,并且使利用关系数据库的部分能力变得没有可能。例如:
1. 不支持开放式加锁
2. 对批量更新的支持极差(EJB2.0主方法至少使它们有可能,但容器——和EJB QL——在实现它们方面没有提供任何帮助)
3. 从一个对象到单个表的映射概念是有限的,而且EJB2.0规范没有建议EJB容器应该如何解决这个问题。
4. 不支持被映射对象中的继承性。像WebSphere那样的有些EJB容器把这实现为一个专有的扩展。
在现实经验方面,实体组件远远落后于那些最好的O/R映射框架。不过,O/R 映射框架一直是很昂贵的产品(尽管这种状况在2002年似乎正在发生改变。)据笔者所知,目前还没有任何开源产品提供一个企业强度的O/R映射解决方案(注:不过现在有Hibernate了)。
以下出自Rod Johnson《J2EE without EJB》
如果出现下列征兆,就可以考虑使用O/R映射:
1. 针对领域对象的“加载/编辑/存储”流程,例如先加载一条产品记录,对其进行修改。
2. 对象以批量查询的方式取出,但更新和删除则是单独进行的
3. 大量对象需要积极的缓存(通常出现在“读操作远多于写操作”的情况下,如Web应用)
4. 在领域对象与数据库表/字段之间有一个相当自然的对应关系。
5. 不需要对SQL进行特别的优化。在大多数时候,好的O/R映射解决方案可以把生成的SQL优化得相当好,例如Hibernate的“方言”(dialect)支持;但一些特殊的SQL优化只有在完全关系型的方式下才可能进行。
O/R映射两大好处:
1. 可以不必编写重复的JDBC代码处理领域对象的实例(意味着编写的代码少,缩短开发周期;进一步意味着维护的代码少,意味着容易维护)
2. 透明持久化:完善的O/R映射工具可以在事务提交时自动将修改后的数据持久化到数据库。正如前面所说,只有真正被修改的数据才会被提交,应用代码无需进行任何脏数据检查。
Entity Bean的败笔(指的是EJB2.0)
1. EJB QL(CMP查询语言——注:与之对比的是Hibernate的HQL)的能力非常有限,迫使开发者不得不编写SQL语句或者依赖于应用服务器厂商专有的扩展功能。
2. Entity Bean的性能经常很糟糕,因为组件管理和方法拦截造成了巨大的开销,尤其是在处理大结果集时更加明显。只有对于大量缓存的对象,它的性能还算差强人意。
EJB2.1中的Entity Bean终于开始走向一个可用的O/R映射解决方案了,但也仅仅是“很多应用可以用它来实现”而已,还根本无法与那些更易用、更强大的持久化技术相匹配。由于Entity Bean并不是用于持久化细颗粒度领域对象的合适方案。
以下出自《Pro Hibernate》
为什么不用EJB来存储,显示,查询数据库中的数据呢?严格的说,EJB服务器支持两种类型的持久化,就是BEAN管理的持久化(BMP)和容器管理的持久化(CMP)。在BMP中,Bean自己负责执行所有的SQL语句来完成存储和查询数据。换句话说,我们自己要去编写JDBC逻辑代码。另一方面,CMP是由容器来执行存储和检索bean数据的工作。
我们这里不选择EJB的原因如下:
1 CMP实体bean需要和数据表一对一的映射
2 它们很慢
3 有时候要人工参与的去决定哪一个bean字段对应表的哪一列
4 它们对方法命名有要求
5 EJB的容器是重量级的
6 它们和容器依赖强,不容易移植
下面看看Hibernate的特点:
1 不需要强制映射一个POJO到一个表,不强制一对一的关系
2 尽快启动并加载它的配置文件时会对性能有些负载,但总的来说,它是很快的工具
3 和容器没有强依赖,很方便的移植
4 可以很轻松的处理serializable POJOs
以下出自网上评论
entity bean是我见过的O/R mapping中最差的一个。它对实体的描述能力太弱(实际上不支持继承),对实体对象的限制太多(要求继承接口),查询能力太弱(不支持动态查询,更不支持非对象查询)。现在就连Sun的人都已经不推荐用entity bean了,他们用JDO。在O/R mapping这里,Hibernate和Castor都比entity bean要好。
性能
以下出自Rod Johnson《J2EE设计开发编程指南》
实体组件性能主要取决于EJB容器的实体组件高速缓存策略,而高速缓存又取决于该容器所运用的加锁策略。
使用实体组件可能会产生比主流的持久性替代方法更糟糕的性能,除非你的应用服务器有一个有效的分布式和事务性实体组件高速缓存器,否则开发人员需要把大量的精力投入到多部署上。在后一种情况中,性能将由应用的性质来决定:①对数据主要进行只读访问的应用将会运转得很好,而②对数据主要进行写访问的应用从高速缓存中将得不到什么好处。
这为什么重要呢?因为没有高效的高速缓存,实体组件性能可能会非常差。
来自实体组件模型的高效性能取决于下列条件:
1. 数据在被访问时可能被修改。除了只读实体的专有支持外,实体组件采用一个读—修改—写模型。
2. 修改发生在个别的被映射对象级别上,而不是作为一个集合操作(也就是说,更新可以利用Java中的个别对象来有效地完成,而不是对一个RDBMS中的多个元组进行更新)
实体组件在许多情况下为什么有性能问题呢?
1. 实体组件使用一种“以一概全”的方法。正如我们使用RDBMS时所见过的,实体组件抽象可能会使有效地访问持久性数据变得不可能。
2. 实体组件约定是严格的,进而使编写有效的BMP代码变得不可能。
3. 调整实体组件数据存取是很困难的,无论我们使用BMP还是CMP。
4. 实体组件具有相当大的运行时开销,即便使用本地而不是远程接口。(一个实体组件将始终比普通Java类有一个大得多的开销)
5. 在实践中,实体组件性能常常会下降到O/R映射性能,而且不保证一个J2EE应用服务器供应商在这个领域内拥有很强的专门技能。
实体组件运行效率特别差,而且由于大型结果集的缘故而耗用过多的资源,尤其在结果集(比如搜索结果)没有被用户修改的时候。实体组件对总是在个别记录级别上被修改的数据有最佳的运行效率。
BMP中蕴含的性能问题:n+1查询发现者问题
问题本质:BMP实体的约定要求开发人员实现发现者来返回实体组件的主键,而不是返回实体。
例子:使用SQL(这就是所谓的n+1查询发现者问题中那个1)查询5000个User,返回后,EJB容器会创建或重用5000个User实体,然后根据每个主键用来自5000个独立查询(当然查询是使用select语句了,这就是所谓的n+1查询发现者问题中那个n)的数据填充这些User实体。这就意味着总共有n+1条select语句了。
复杂性
EJB2.0需要3个Java文件两个描述文件才能搞定,而Hibernate则只需要1个Java文件和一个描述文件,如果数据库中有1000张表要映射成EJB实体和Hibernate的POJO,那么就是5000个文件和2000个文件的差别,那如果需要1万个EJB实体,就意味着是5万和2万的差别了。
结论
总而言之,EJB实体bean(就是J2EE的O/R Mapping方案)实现的功能只是Hibernate实现功能的一个子集,而且采用了笨拙和低效的方法实现的,而且复杂性远高于Hibernate(开发,部署,维护),此外其可测试性方面和Hibernate也没法相比。
参考资料
1. [原创]Pro Hibernate 3笔记和小结(2)之第一章Hibernate入门
2. Rod Johnson的两本巨著