实现跨事务的Hibernate懒加载
最近使用Eclipse插件制作一个项目管理工具,由于涉及的数据结构比较复杂,简单的展示一棵导航树就涉及到至少20个表关联查询(其中包含大量的自关联及循环关联),刚开始数据量较小时尚可应付,当运行一段时间后,数据量达到万级以后,性能就相当差了,仔细看了一下,由Hibernate翻译过来的一条SQL就显示了十屏以上,汗一个先,所以下定决心优化一下。
首先想到是数据库层面的优化,由于数据结构已经固定,无法再通过增加删除表来优化,剩下的策略只能是加加索引,增加视图来减化复杂性(要命的是视图无法作更新操作),但收效甚微。
其次想到的是使用Hibernate的二级缓存来优化性能,但由于CS应用的特点决定,数据操作一定是发生在并发的环境中,所以贸然打开二级缓存会带来更大的麻烦(所谓的脏数据),所以基本放弃了,试着针对几个常量表开了只读的二级缓存,但效果依然不明显。
最后想到的是使用原来作WEB应用时常用的OpenSessionInView方式来作基于长事务的懒加载,但与WEB应用不同,WEB应用可以严格的界定事务边界,简单的加一个Filter就可以实现了,但CS架构的应用没有明显的边界,数据的查询和更新发生在整个生命周期的任何时刻,想破了头也没想到好的方法来实现,至此基本打算放弃了。
这几天翻看Hibernate源码,发现Hibernate实现懒加载并不强制要求被加载对象与宿主对象必须在同一个事务中,只要有一个可用的Session,并且在这个Session的上下文(其实就是Session的一级缓存)中注册相应的持久类信息即可,而这个Session是否与被加载对象的宿主处在同一个事务中,Hibernate并不关心,基于此准备实现一个跨事务的懒加载机制来简化复杂业务下的大数据加载问题。protected Object initialize(final Object proxy) { if (proxy instanceof HibernateProxy) return initializeHibernateProxy((HibernateProxy) proxy); else if (proxy instanceof PersistentCollection) return initializePersistentCollection((PersistentCollection) proxy); return proxy; } private Object initializePersistentCollection(final PersistentCollection persistentCollection) { if (persistentCollection.getRole() != null && !persistentCollection.wasInitialized() && ((AbstractPersistentCollection) persistentCollection).getSession() == null) { return getHibernateTemplate().execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { final SessionImplementor sessionImplementor = (SessionImplementor) session; final CollectionPersister collectionPersister = sessionImplementor.getFactory().getCollectionPersister(persistentCollection.getRole()); sessionImplementor.getPersistenceContext().addUninitializedDetachedCollection(collectionPersister, persistentCollection); persistentCollection.setCurrentSession(sessionImplementor); persistentCollection.forceInitialization(); sessionImplementor.getPersistenceContext().clear(); persistentCollection.unsetSession(sessionImplementor); return persistentCollection; } }); } return persistentCollection; } private Object initializeHibernateProxy(final HibernateProxy proxy) { final LazyInitializer lazyInitializer = proxy.getHibernateLazyInitializer(); if (lazyInitializer.isUninitialized() && lazyInitializer.getSession() == null) { return getHibernateTemplate().execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { lazyInitializer.setSession((SessionImplementor) session); lazyInitializer.initialize(); lazyInitializer.unsetSession(); return proxy; } }); } return proxy; }
按道理,为了尽可能的减少对原有结构的侵入,应该对所有实体类的GET方法作代理,判断数据是否已被加载,如果没加载,则调用initialize方法先加载数据后再返回,可是我至今没有发现Hibernate提供类似的Intercepter,应该是可以使用第三方的代理来实现,这块我再研究一下,目前的实现方式是对实体类的GET方法硬编码:
public ComponentTypeBean getComponentTypeBean() { return (ComponentTypeBean) initialize(componentTypeBean); } @SuppressWarnings("unchecked") public Set<PageComponentBean> getPageComponentBeans() { return (Set<PageComponentBean>) initialize(pageComponentBeans); }
显然这样作可以实现预期效果,但并不理想,不知道Hibernate是否提供了实体类的GET代理,等有时间再翻一下文档。
至此,基于跨事务的懒加载已经实现,当然方法可能用的并不是很正确,希望有高人再指点一下。经测试实际效果非常理想,总结一下使用跨事务懒加载:
好处:
1.原来长篇大论的HQL全部可以简化为单表查询了,换句话说再也不用费尽脑汁的去想需要写几个joinfech了,只要查询出主表数据即可,随时随地调用GET方法即可实现从表数据的懒加载,彻底告别couldnotinitializeproxy-noSession,couldnotinitializeproxy-theowningSessionwasclosed等异常。
2.将大SQL拆成若干个单表查询的小SQL,在特定环境下会为显著提升系统性能,改善用户体验。
坏处:
1.会造成SQL量增大,用的不好反而会造成系统性能下降。
2.与OpenSessionInView不同,跨事务懒加载破坏了Hibernte原有的缓存体系,无法发挥事务内缓存带来的性能提升。
原文地址: http://www.coolfancy.com/log/31.html更多精彩原创文章请关注笔者的原创博客:http://www.coolfancy.com