解惑:在spring+hibernate中,只读事务是如何被优化的。/** *作者:张荣华(ahuaxuan) *2007-06-28 *转载请注明
解惑:在spring+hibernate中,只读事务是如何被优化的。
/**
*作者:张荣华(ahuaxuan)
*2007-06-28
*转载请注明出处及作者
*/解惑:在spring+hibernate中,只读事务是如何被优化的。??????? 大家都知道,spring+hibernate的环境下,spring对只读事务会有特别的优化,那么spring是如何做到这个优化的呢??????? ?Without ejb中写到,当事务被标识为只读事务时,某些可以针对只读事务进行优化的资源就可以执行相应的优化措施,比如说hibernate的session在只读事务模式下不会尝试检测和同步持久对象的状态的更新。另外还写到jdbc的connection可以通过调用setReadOnly(true)来切换到只读事务模式上来;但是大多数jdbc driver会忽略掉他。?我们知道spring中所谓的只读事务就是通过设置session的flushmode为never来实现的(http://www.iteye.com/topic/87426)。那么把flushmode设置为never能给我们带来什么呢?我们来看一下hibernate中JDBCTransaction中的方法:java 代码
- publicvoid?commit()?throws?HibernateException?{ ?? ???????if?(!begun)?{ ??
- ???????????thrownew?TransactionException("Transaction?not?successfully?started"); ?? ???????} ??
- ? ?? ???????log.debug("commit"); ??
- ? ?? ???????if?(?!transactionContext.isFlushModeNever()?&&?callback?)?{ ??
- ???????????transactionContext.managedFlush();?//if?an?exception?occurs?during?flush,?user?must?call?rollback() ?? ???????} ??
- //也就是在这里会判断是否需要刷新一级缓存中的持久对象,如果session的flushmode不为//never而且需要回调的话,那么就刷新一级缓存中的持久对象,向数据库发送sql语句 ?? ? ??
- ???????beforeTransactionCompletion(); ?? ???????if?(?callback?)?{ ??
- ???????????jdbcContext.beforeTransactionCompletion(?this?); ?? ???????} ??
- ? ?? ???????try?{ ??
- ???????????commitAndResetAutoCommit(); ?? //提交事务,并且把事务的commit方式设置为auto,是不是和spring在事务开始和事务结束//时设置session的flush?mode的方式是一样的呀。 ??
- ???????????log.debug("committed?JDBC?Connection"); ?? ???????????committed?=?true; ??
- ???????????if?(?callback?)?{ ?? ??????????????jdbcContext.afterTransactionCompletion(?true,?this?); ??
- ???????????} ?? ???????????afterTransactionCompletion(?Status.STATUS_COMMITTED?); ??
- ???????} ?? ???????catch?(SQLException?e)?{ ??
- ???????????log.error("JDBC?commit?failed",?e); ?? ???????????commitFailed?=?true; ??
- ???????????if?(?callback?)?{ ?? ??????????????jdbcContext.afterTransactionCompletion(?false,?this?); ??
- ???????????} ?? ???????????afterTransactionCompletion(?Status.STATUS_UNKNOWN?); ??
- ???????????thrownew?TransactionException("JDBC?commit?failed",?e); ?? ???????} ??
- ???????finally?{ ?? ???????????closeIfRequired(); ??
- ???????} ?? ????} ??
我们看一下那个managedFlush()方法,这个方法主要就是刷新一级缓存的一个方法:
java 代码- public?void?managedFlush()?{ ?? ????????if?(?isClosed()?)?{ ??
- ????????????log.trace(?"skipping?auto-flush?due?to?session?closed"?); ?? ????????????return; ??
- ????????} ?? ????????log.trace("automatically?flushing?session"); ??
- ????????flush(); ?? ????????//刷新这个session实例的一级缓存。 ??
- ????????if?(?childSessionsByEntityMode?!=?null?)?{ ?? ????????????Iterator?iter?=?childSessionsByEntityMode.values().iterator(); ??
- ????????????while?(?iter.hasNext()?)?{ ?? ????????????????(?(Session)?iter.next()?).flush(); ??
- ????????????} ?? ????????}//刷新该session的子session的一级缓存。 ??
- ????} ??
我们知道如果session的flushmode为never的时候,以上的方法是不会调用的,这样就可以省去很多flush的开销。于是命题就变成了flush操作有哪些开销了。也许你要问flush和不flush有什么样的区别,在开销上有多大的区别呢。要看明白hibernate是怎么做flush的,那就必须要知道观察者模式了,实际上session是一个被观察者(subject),而真正执行flush的是一个观察者(observer),我们来看一下下面这个图:(这个图是我画在纸上然后用手机拍下来的)?从这里面我们可以看到flush实际上是由DefaultFlushEventListener来执行的,而且sessionimpl默认的只注册了一个FlushEventListener实例(为什么只有一个还要这样做,我估计他是为了扩展的需要,不知道3.2中是否就不止一个了呢?),这个DefaultFlushEventListener最终执行了flush的方法:?
java 代码- public?void?onFlush(FlushEvent?event)?throws?HibernateException?{ ?? ????????final?EventSource?source?=?event.getSession(); ??
- ????????if?(?source.getPersistenceContext().hasNonReadOnlyEntities()?)?{ ?? ???????????? ??
- ????????????flushEverythingToExecutions(event); ?? //这个方法是flush前的准备工作,它把需要被flush的实体,集合,等等放到需要被flush ??
- //的一个队列中 ?? ????????????performExecutions(source); ??
- //这个方法是最重要的,因为在这里才是真正的执行sql语句,并且负责更新二级缓存(如果你//配置了二级缓存的话) ?? ????????????postFlush(source); ??
- //负责flush后的善后工作,比如说一个对象不再被另外一个对象关联了,那么就把这个对象//从一级缓存重剔除,等等。 ?? ????????????if?(?source.getFactory().getStatistics().isStatisticsEnabled()?)?{ ??
- ????????????????source.getFactory().getStatisticsImplementor().flush(); ?? ????????????} ??
- ???????????? ?? ????????} ??
- ????} ??
?由此我们看到hibernate在执行flush操作的时候还是做了不少事情的,它不但要把持久对象刷到数据库,而且还要把其管理的对象也都刷到数据库中,这是一个很大的操作。同时如果你使用了二级缓存,flush操作也会涉及到它,而且在flush时还要判断哪些时插入的,哪些是更新的,哪些是删除的等等,flush完了还得更新一级缓存等。?其实我只是对flush作了最简单的概括和描述,事实上从代码上看来它远比我们想象的要来得复杂的多。?在对flush简单得了解了之后,我们再来讨论一下:为什么要把查询设置为只读事务。因为一个本来只是查询的操作,却要在事务提交时多做这么多事情,这显然是不合理的,所以hibernate才给session的设置了这么一个flushmode,那么只要这个mode为never,就可以免去这些不必要的操作。而spring在对hibernate的支持时也充分的考虑到了这一点,所以就把只读事务的session的flush mode设置为了never。这样我们事务提交时就不会执行flush操作了。?总结:所以说,我们在使用spring时一定要注意把查询的操作定义成只读事务,这个可以给我们带来不必要的开销,比如看如下配置。<property name="transactionAttributes">?????????? <props>????????????? <prop key="do*">PROPAGATION_REQUIREDprop>????????????? <prop key="get*"> PROPAGATION_REQUIRED,readOnlyprop>????????????? <prop key="load*">PROPAGATION_REQUIRED,readOnlyprop>?????????????? <prop key="find*">PROPAGATION_REQUIRED,readOnlyprop>????????????? <prop key="list*">PROPAGATION_REQUIRED,readOnlyprop>?????????? props>property>?或者事务的传播途径最好能设置为supports(运行在当前的事务范围内,如果当前没有启动事务,那么就不在事务范围内运行)或者not supports(不在事务范围内执行,如果当前启动了事务,那么挂起当前事务),也就是说查询操作其实可以不必要真正的开启一个数据库事务,因为开启一个真正的数据库事务又会给我们带来一点点可以忽略不计的开销。下面是一个例子<property name="transactionAttributes">?????????? <props>????????????? <prop key="do*">PROPAGATION_REQUIREDprop>????????????? <prop key="get*">PROPAGATION_SUPPORTS,readOnlyprop>????????????? <prop key="load*">PROPAGATION_SUPPORTS,readOnlyprop>?????????????? <prop key="find*">PROPAGATION_SUPPORTS,readOnlyprop>????????????? <prop key="list*">PROPAGATION_SUPPORTS,readOnlyprop>?????????? props>property>?
作者:张荣华,未经作者同意不得随意转载!
1 楼 annegu 2007-07-23 也就是说在使用spring+hibernate的时候,只读事务就是少一个flush对吧?
那使用spring+jdbc的时候,只读事务有哪些优化措施呢?是不是说就没有优化了呀,因为jdbc没有什么flush的说法呀。
2 楼 ahuaxuan 2007-07-24 annegu 写道也就是说在使用spring+hibernate的时候,只读事务就是少一个flush对吧?
那使用spring+jdbc的时候,只读事务有哪些优化措施呢?是不是说就没有优化了呀,因为jdbc没有什么flush的说法呀。
直接使用jdbc好像没有什么优化不优化之说,我不知道把connection设置为readonly到底能带来多少性能的提高,估计没有,而且很多jdbc驱动会忽略这选项,所以直接使用jdbc应该是没有什么特别的优化的 3 楼 xufei0110 2008-05-28 喜欢这样的 文章
在 java方面我还是一个初学者
虽然也能用 Struts Spring Hibernate 等
但 还没有能力 读懂 其源代码
不过 以后会加油的 4 楼 罪恶的小手 2008-06-03 说实话还是有的没看懂,,
也许是我刚接触的问题吧,,
以后努力看吧,,