/**
*作者:张荣华(ahuaxuan)
*2007-06-28
*转载请注明出处及作者
*/
*作者:张荣华(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_REQUIRED
prop >
<
prop
key
=
"get*"
>
PROPAGATION_REQUIRED,readOnly
prop >
<
prop
key
=
"load*"
>
PROPAGATION_REQUIRED,readOnly
prop >
<
prop
key
=
"find*"
>
PROPAGATION_REQUIRED,readOnly
prop >
<
prop
key
=
"list*"
>
PROPAGATION_REQUIRED,readOnly
prop >
props >
property >
或者事务的传播途径最好能设置为
supports
(运行在当前的事务范围内,如果当前没有启动事务,那么就不在事务范围内运行)或者
not supports
(不在事务范围内执行,如果当前启动了事务,那么挂起当前事务),也就是说查询操作其实可以不必要真正的开启一个数据库事务,因为开启一个真正的数据库事务又会给我们带来一点点可以忽略不计的开销。下面是一个例子
<
property
name
=
"transactionAttributes"
>
<
props
>
<
prop
key
=
"do*"
>
PROPAGATION_REQUIRED
prop >
<
prop
key
=
"get*"
>
PROPAGATION_SUPPORTS,readOnly
prop >
<
prop
key
=
"load*"
>
PROPAGATION_SUPPORTS,readOnly
prop >
<
prop
key
=
"find*"
>
PROPAGATION_SUPPORTS,readOnly
prop >
<
prop
key
=
"list*"
>
PROPAGATION_SUPPORTS,readOnly
prop >
props >
property >
作者:张荣华,未经作者同意不得随意转载!