前段时间一直在断断续续地给俱乐部开发一个开源网站。其实这个网站本身没有什么难度,是一个很普通的开源社区,之前也做过类似的项目。但这一次主要的区别 在于首次在实际项目中使用Hibernate来进行对象的持久化。虽然之前学过Hibernate,但是实际用起来发现理论学习和实践的差距还是比较大 的,尤其是Hibernate缓存部分是最为" 闹心"的地方。现在这个网站也顺利开发完了,也正好总结一下在使用Hibernate上的一些经验,与大家分享。
Hibernate中的Session的缓存中存储的是数据库中数据的拷贝,但是以对象的形式存储的。而缓存需要依据具体的应用范围,来决定自己的生命周期。从而我们可以分为:
事务缓存:即缓存的生命周期依赖于具体的事务的生命周期,当事务结束后,缓存也就结束了。
进程缓存:其生命周期与进程一致,缓存被进程内的若干事务所共享,从而存在并发访问的情况。所以可能需要采用事务隔离机制。(散装数据形式:适用于并发访问的情况)
集群范围:这主要是在集群环境中,缓存被一个主机或者多台主机所共享。缓存中的数据通过复制传递给集群中的各个节点,而节点之间通过通信来保持数据的一致性。在集群环境中,通常采用对象的散装数据结构的形式存储。
这里就不再对并发访问等问题展开论述了,我们就还是讨论Hibernate的缓存结构吧。
Hibernate提供了两级缓存:第一级缓存是Session缓存,它的范围是事务缓存。对于Hibernate而言是无法被删除的。在这一级缓存中,被存储的对象实例都有一个唯一的ID;第二级缓存是可配置的缓存,由SessionFactory负责管理。它是进程范围缓存,对应于整个SessionFactory对象的生命周期。我们可以通过具体的缓存适配器(Cache Provider)将具体的缓存实现集成到Hibernate。在这里我们就主要讲第一级缓存。因为在这个项目中我们就只需要第一级缓存。
在这个项目中,我们将对数据和对象的持久化都封装在了DAO对象中。从一定程度上为减轻了我们的工作量。原来以为只需要调用相应的DAO方法就行。后来发现一些被删掉的对象还会跑出来,添加的东西有时候又出不来。最后才发现我们在封装DAO时没有考虑缓存的问题(说实话,我们也没有考虑事务,呵呵!)。那下面就具体地讲讲Hibernate的缓存吧。
当应用程序通过Session的增删改查的方法时,如果Session的缓存中还不存在相应的对象,Hibernate就会将相应的对象加入到第一级缓存中。当清理相应的缓存时,就会将缓存中的数据同步到数据库中。我们可以调用evict(Object o)和clear()来管理Session的缓存。
news1 = ndao.findById(Integer.parseInt(newsId1));
news2 = ndao.findById(Integer.parseInt(newsId2));
Transaction transaction = HibernateSessionFactory.getSession().
beginTransaction();
transaction.begin();
ndao.delete(news1);
HibernateSessionFactory.getSession().evict(news1);
transaction.commit();
HibernateSessionFactory.getSession().clear();
HibernateSessionFactory.closeSession();
采用clear()和evict()方法来管理第一级缓存并不能提供性能。但可以通过过滤、延迟加载等方式来节省内存的开销。特别是对批量的对象处理,更能显示出之间的差别。当然在批量处理中,每处理一个对象马上将其从内存中释放,这样能提高一点应用程序的性能,但是处理的次数还是没有变。我认为可以通过直接使用SQL语句进行批量的处理:
tx = session.beginTransaction();
Connection con = session.connection();
....//do something by SQL
tx.commit();
在这里我们仍可以应用Session来保证查询的事务。在实际的应用中,我们可以针对具体的数据库,通过调用其特有的存储过程来优化。