谭明智的空间

我们一直在努力....

hibernate,让我欢喜让我忧

标签: hibernate 系统问题 系统优化

 

 前几天,高丽又开始对我大喊大叫了,怎么网上药店的新产品评论又不在前台显示了。我不得不再对我的代码再审查一遍,让她弄得我都快自我怀疑症了。看了半天发现我的代码没问题啊,问题出在什么地方呢?经过仔细的检查发现,问题出现在评论回复后的静态页生成上。由于前台是静态页面,每次评论审核通过后都要刷新静态页。同事写的生成静态页的操作出现了异常。按理说出现异常要回滚啊,但是原因就在于数据库的事务是作用在DAO上而非完整的操作上,造成了保存审核状态的事务结束后,后面的异常不能让前面的操作回滚,导致了以上的问题。
 
    以上的事例只是我们系统问题的一个缩影,通过我曾经参与过的网上药店、KOA,还有我负责设计开发的软件实验室,打开系统错误日志,大部分的错误都来自于hibernate。想必大家都一直被hibernate折磨着吧,我最近也被实验室的一些BUG给折腾的茶饭不香,经过了从上线到现在的维护,存在的问题一个个的解决了,现在系统慢慢的趋于稳定了,但是花去的大量的时间和精力的确让我感到很不爽,最近痛定思痛,针对hibernate的运用问题审视了我们的各个系统,对于出现的问题和怎样合理的使用,简单的分析一下,希望我们以后能避免这些问题。
1、 由事务管理谈框架使用
上面那个小故事其实就是事务的不合理使用。在网上药店和KOA,存在大量这样的问题,事务的作用域在每个DAO的原子操作上,其实这样的做法违背了事务的原子性—要么都执行成功,要么都不执行,KOA经常锁表也可能跟事务的不合理使用有关系。对于hibernate的事务,有三种方式:JTA ,JDBC transaction,User transaction,和JTA隐式事务(不需要显式的代码管理实务,由server管理,多用于分布式事务管理。OECP项目采用的方式)不同,我们在web开发中经常使用的是JDBC transaction,需要我们手工开启和关闭事务。以下是hibernate官方教程上的一段话:
Custom transaction interceptors
To remove transaction demarcation from your data access code you might want to write your own interceptor that can begin and end a transaction programmatically (or even declaratively). This is a lot easier than it sounds, after all, you only have to move three methods into a different piece of code that runs every time a request has to be processed. Of course more sophisticated solutions would also need to handle transaction propagation, e.g. if one service method calls another one. Typical interceptors are a servlet filter, or an AOP interceptor that can be applied to any Java method or class.
这也是官方提倡的方式,通过自定义的AOP拦截机或者过滤器来管理实务,将一个请求作为一个完整的业务操作,全部成功才算成功,只要其中一个操作失败,将所有的操作回滚,保证了事务的原子性。百洋软件实验室吸取了以往的教训,通过Struts2自定义的事务拦截机进行统一的事务管理,保证了业务操作的一致性。
为什么网上药店和KOA没有采用这种方式,原因在于JSF框架本身。JSF是基于事件的框架,本身不具备拦截机的功能,而是采用的事件监听机制。通过事件监听可以实现AOP的功能,我们也可以配置自定义全局的生命周期监听器—PhraseListener来实现事务的处理,在beforePhrase中开启事务,在afterPhrase关闭事务。但是存在的问题是监听器不容易拿到当前的操作,从而无法操纵当前操作,当出现异常错误的时候,我们却不容易捕获并回滚事务。由于JSF不具备事务管理的功能,自定义异常机制也不好使,所以JSF作为表现层框架一般会结合EJB和spring等业务逻辑层的框架,而EJB和spring都提供了完整的事务管理,AOP机制,并且可以结合JPA和hibernate作为ORM。这也是为什么SSH框架组合是JEE web开发的最流行的框架组合。在web系统的架构设计中,我个人推荐JSF+EJB(jboss seam),JSF+Spring+Hibernate,SSH的框架组合作为整体技术框架体系,然后结合其他的技术作为辅助。
2、 让hibernate或者程序来生成主键
在百洋软件实验室的设计上,由于对hibernate掌握不是非常好,出现了一些的问题,造成日后维护成本稍高。尤为突出的是主键生成策略的选择上。在百洋软件实验室的项目中,大部分的主键都是采用的数据库自增长的方式。优点是不需自己维护主键,整形的主键检索的效率比较高。但是缺点是并发较差,依赖于数据库,更为突出的问题是,在事务范围内,程序无法获得主键值,不能手工的进行表级联操作,不能在保存的同时根据主键进行相应的处理。比如在保存新闻的同时,生成一个按照主键取名的静态页文件,就无法直接做到。对于主键的讨论,可以参考我以前的文章:OECP中主键使用及生成策略探索
3、 合理的使用级联
使用级联的目的是为了提高执行的效率和方便性。但是一定要合理使用,不该使用级联的地方不仅仅不能带来效率的提升反而增加出错的几率。比如两个对象配置了级联关系,如果在同时保存两个对象时,就很容易报一个对象有两个相同的标识的错误。这方面的错误也在百洋软件实验室项目中广泛的碰到,但是由于不能获取主键,不得不使用级联保存来完成两个表的关联。所以如果级联使用不好,建议还是多写一些程序吧。
4、 透过缓存谈系统的优化
效率永远是软件开发不变的话题。我们在使用hibernate的时候突出的感觉到hibernate的效率不高,打印出的sql杂乱繁多。毕竟hibernate是对JDBC的封装,单从SQL操作上看,hibernate的sql执行效率不高。然而作为一个强大的ORM框架,hibernate从多个方面对效率进行了优化,突出的表现在缓存的使用上。首先hibernate的session缓存给我们提供了大量的自动化的缓存操作,由于缓存的存在,减少了对数据库的连接次数,将最影响性能消耗的部分降了下来。不过hibernate一级缓存也给我们带来了很多的困恼,数据的脏读,实时性问题。我们在以往的系统中大量的使用session.clear()操作来解决这个问题,但是这不是非常好的方式,大大的破坏了session缓存存在的意义。Hibernate为我们提供了FlushMode,CacheMode来解决这个问题,而我们没有很好的利用。同时上面介绍的事务的不合理使用也是一个原因,SELECT操作加不加事务都是一样的认识也是有失偏颇。并且预先抓取、延迟加载都在session级别提供的优化方案。hibernate还为我们提供了基于插件式的二级缓存,通过配置的方式,我们可以进行全局性的对象缓存,可以使用查询缓存,通过对经常不变化但经常使用的数据进行二级缓存,可以有效的降低对数据库的执行频率,提高数据检索的效率。在以往的系统中,我们都忽视了数据库连接池的配置,都是采用的hibernate内置的连接池。而hibernate官方说明中明确指出,hibernate内置连接池仅限开发环境使用,正式环境一般要配置第三方的连接池。这个问题在KOA经常出现相关的bug后得到认识,并为现有的系统配置了C3PO的连接池。再补充一点,请在正式环境中关闭hibernate打印SQL的功能,以减少没必要的IO消耗。
Hibernate的优化还值得我们更深层次的研究,系统的效率和hibernate的正确使用有着很直接的关系,当我们出现了问题,我们不要简单的认为是hibernate的问题,而更多的是我们应用的问题。
同时EHcache和OScache等这些缓存插件还为我们提供了页面缓存的能力,对于数据实时性要求不高的互联网应用,我们可以尝试使用。我个人认为现在的若干系统,过度的依赖于页面的静态化,提升效率的同时造成了开发和维护的复杂性,系统的综合成本没有降低,可以考虑多使用缓存来做系统效率的首选解决方案。
5、 做基于hibernate的系统设计方案
如我们在开发系统前,我们确定要使用hibernate而不会随意切换,那么在数据库设计和底层技术架构上尽量针对hibernate的特点进行设计。
   Hibernate
                   
倾向于细颗粒度的设计,面向对象,将大表拆分为多个关联关系的小表,消除冗余column,通过二级缓存提升性能(DBA比较忌讳关联关系的出现,但是 ORM的缓存将突破关联关系的性能瓶颈);Hibernate的性能瓶颈不在于关联关系,而在于大表的操作
            iBATIS

                   
倾向于粗颗粒度设计,面向关系,尽量把表合并,通过表column冗余,消除关联关系。无有效缓存手段。iBATIS的性能瓶颈不在于大表操作,而在于关联关系。
很多情况,我们在数据库的设计上只考虑了业务的需求和架构设计,而忽视了基于特定技术的设计。这也为未来系统的开发、运行和维护带来难度和复杂度。
以上,是我在两年多使用hibernate开发亲身经历后发现的一些问题及思考,hibernate是一个优秀的ORM框架,但学习门槛高,很好的掌握更难,所以它是一把双刃剑,使用的好,可以提高系统开发的速度,降低维护的成本,提升系统的效率,但掌握不好就会反受其害,得不偿失。我希望我们能及时的发现系统的问题,并加以改进,同时在未来的设计和开发中,更加合理的设计和使用,带给我们的是欢喜而不是忧愁。
 
  
这个系列的文章,可以通过访问:小工纸上谈兵来阅读讨论。所有的言论都是我个人的观点,仅仅是一些思考,不夹带任何其他成分,希望大家能积极的讨论,提出建议和批评。
 
提供该文档的机构为 百洋软件研究实验室,更多的博客文章可以到 百洋软件研究实验室博客查看。该文档附件欢迎各位转载,但是在没有获得文章作者许可之前,不得对文章内容或者版权信息进行更改,版权归百洋软件研究实验室所有,仅此声明。

附件:


    评分: 请先登录再投票,同一篇博客一月只能投票一次!
    无人投票

相关博客:


评论

游客 2010-9-17 13:10:40  
经验总结,值得参考
李浩 2011-1-4 13:38:35   回复
我也在用ssh组合开发网站,一直以来在这3个主要的开源产品上没有lz的问题。

我对于lz遇到的问题分别说下我的方法:
1.事物,用dao,把hiber级包装到service里面调用,事物用spring配置在service上。
2.自动id有的是速度在插入可能慢,但是在后期做分表分区,还是可以解决的。还有,自动id主键查询速度很快。
3.不使用任何join操作,也就没有级联的问题
4.基于第三点设计,缓存用memcached
5.基于3,4两点,也就没有这个问题!

发表评论

关注此文的人们还关注