Hibernate进行时

有关Hibenrate及其相关工具的主页
随笔 - 0, 文章 - 16, 评论 - 29, 引用 - 0
数据加载中……

HibernateTemplate中HibernateCallback的事务

目的:使用HibernateTemplate执行execute(new HibernateCallback())方法,从HibernateCallback中得到session,在此session中做多个操作,并希望这些操作位于同一个事务中。
      如果你这样写(1):
      
public static void main(String ss[]) {
        CtxUtil.getBaseManager().getHibernateTemplate().execute(
new HibernateCallback() {
            
public Object doInHibernate(Session session) throws HibernateException, SQLException {
                
// 保存stu1
                Student stu1 = new Student();
                stu1.setName(
"aaaa");// 在数据库中,name字段不允许为null
                session.save(stu1);
                session.flush();//实际上,如果不是程序员"手痒"来调用这个flush(),HibernateTemplate中session的事务处理还是很方便的

                Student stu2 
= new Student();
                session.save(stu2);
// 没有设置name字段,预期会报出例外
                session.flush();
                
return null;
            }

        }
);

    }
      你期望spring在执行完execute回调后,在关闭session的时候提交事务,想法是很好的,但spring并不会这么做.让我们来看看在Hibernate的源代码中,session.beginTransation()做了什么事。看如下代码(2):
public Transaction beginTransaction() throws HibernateException {
        errorIfClosed();
        
if ( rootSession != null ) {
            
// todo : should seriously consider not allowing a txn to begin from a child session
            
//      can always route the request to the root session
            log.warn( "Transaction started on non-root session" );
        }

        Transaction result 
= getTransaction();
        result.begin();
        
return result;
    }
这个方法中的result是一个org.hibernate.transaction.JDBCTransaction实例,而方法中的getTransaction()方法源代码为(3):
public Transaction getTransaction() throws HibernateException {
        
if (hibernateTransaction==null{
            log.error(owner.getFactory().getSettings()
                    .getTransactionFactory().getClass());
            hibernateTransaction 
= owner.getFactory().getSettings()
                    .getTransactionFactory()
                    .createTransaction( 
this, owner );
        }

        
return hibernateTransaction;
    }
再次追踪,owner.getFactory().getSettings() .getTransactionFactory()的createTransaction()方法源代码如下(4):
public Transaction createTransaction(JDBCContext jdbcContext, Context transactionContext)
    
throws HibernateException {
        
return new JDBCTransaction( jdbcContext, transactionContext );
    }
它返回了一个JDBCTransaction,没什么特别的。
在代码2中,执行了result.begin(),其实也就是JDBCTransaction实例的begin()方法,来看看(5):

public void begin() throws HibernateException {
        
if (begun) {
            
return;
        }

        
if (commitFailed) {
            
throw new TransactionException("cannot re-start transaction after failed commit");
        }

        log.debug(
"begin");
        
try {
            toggleAutoCommit 
= jdbcContext.connection().getAutoCommit();
            
if (log.isDebugEnabled()) {
                log.debug(
"current autocommit status: " + toggleAutoCommit);
            }

            
if (toggleAutoCommit) {
                log.debug(
"disabling autocommit");
                jdbcContext.connection().setAutoCommit(
false);//把自动提交设为了false
            }

        }
 catch (SQLException e) {
            log.error(
"JDBC begin failed", e);
            
throw new TransactionException("JDBC begin failed: ", e);
        }

        callback 
= jdbcContext.registerCallbackIfNecessary();
        begun 
= true;
        committed 
= false;
        rolledBack 
= false;

        
if (timeout > 0{
            jdbcContext.getConnectionManager().getBatcher().setTransactionTimeout(timeout);
        }


        jdbcContext.afterTransactionBegin(
this);
    }

在直接使用Hibernate时,要在事务结束的时候,写上一句:tx.commit(),这个commit()的源码为:
public void commit() throws HibernateException {
        
if (!begun) {
            
throw new TransactionException("Transaction not successfully started");
        }


        log.debug(
"commit");

        
if (!transactionContext.isFlushModeNever() && callback) {
            transactionContext.managedFlush(); 
// if an exception occurs during
            
// flush, user must call
            
// rollback()
        }


        notifyLocalSynchsBeforeTransactionCompletion();
        
if (callback) {
            jdbcContext.beforeTransactionCompletion(
this);
        }


        
try {
            commitAndResetAutoCommit();//重点代码,它的作用是提交事务,并把connection的autocommit属性恢复为true
            log.debug(
"committed JDBC Connection");
            committed 
= true;
            
if (callback) {
                jdbcContext.afterTransactionCompletion(
truethis);
            }

            notifyLocalSynchsAfterTransactionCompletion(Status.STATUS_COMMITTED);
        }
 catch (SQLException e) {
            log.error(
"JDBC commit failed", e);
            commitFailed 
= true;
            
if (callback) {
                jdbcContext.afterTransactionCompletion(
falsethis);
            }

            notifyLocalSynchsAfterTransactionCompletion(Status.STATUS_UNKNOWN);
            
throw new TransactionException("JDBC commit failed", e);
        }
 finally {
            closeIfRequired();
        }

    }

上面代码中,commitAndResetAutoCommit()方法的源码如下:
private void commitAndResetAutoCommit() throws SQLException {
        
try {
            jdbcContext.connection().commit();//这段不用说也能理解了
        }
 finally {
            toggleAutoCommit();//这段的作用是恢复connection的autocommit属性为true
        }

    }

上述代码的toggleAutoCommit()源代码如下:
    private void toggleAutoCommit() {
        
try {
            
if (toggleAutoCommit) {
                log.debug(
"re-enabling autocommit");
                jdbcContext.connection().setAutoCommit(
true);//这行代码的意义很明白了吧
            }

        }
 catch (Exception sqle) {
            log.error(
"Could not toggle autocommit", sqle);
        }

    }
      因此,如果你是直接使用hibernate,并手动管理它的session,并手动开启事务关闭事务的话,完全可以保证你的事务(好像完全是废话).
      但是,如果你用的是HibernateTemplate,如同源代码1一样,则不要指望spring在关闭session的时候为你提交事务(罪魁祸首就是在代码1中调用了session.flush())。因为在使用代码1时,spring中得到session的方式如下:
Session session = (entityInterceptor != null ? sessionFactory.openSession(entityInterceptor) : sessionFactory
                .openSession());
简单地说它就是得到了一个session,而没有对connection的autocommit()作任何操作,spring管理范围内的session所持有的connection是autocommit=true的,spring借助这个属性,在它关闭session时,提交数据库事务。,因此如果你在源代码1中加上一句话:
public static void main(String ss[]) {
        CtxUtil.getBaseManager().getHibernateTemplate().execute(
new HibernateCallback() {
            
public Object doInHibernate(Session session) throws HibernateException, SQLException {
                log.info(session.connection().getAutoCommit());
//打印一下事务提交方式
                
// 保存stu1
                Student stu1 = new Student();
                stu1.setName(
"aaaa");// 在数据库中,name字段不允许为null
                session.save(stu1);
                session.flush();

                Student stu2 
= new Student();
                session.save(stu2);
// 没有设置name字段,预期会报出例外
                session.flush();
                
return null;
            }

        }
);

    }
     运行后,它打出的结果是true,也就是说,虽然保存stu2时会报出例外,但如果commit属性为true,则每一个到达数据库的sql语句会立即被提交。换句话说,在调用完session.save(stu1)后,调用session.flush(),会发送sql语句到数据库,再根据commit属性为true,则保存stu1的操作已经被持久到数据库了,尽管后面的一条insert语句出了问题。
     因此,如果你想在HibernateCallback中使用session的事务,需要如下写:
public static void main(String ss[]) {
        CtxUtil.getBaseManager().getHibernateTemplate().execute(
new HibernateCallback() {
            
public Object doInHibernate(Session session) throws HibernateException, SQLException {
                session.connection().setAutoCommit(
false);
                
//保存stu1
                Student stu1=new Student();
                stu1.setName(
"aaaa");//在数据库中,name字段不允许为null
                session.save(stu1);
                session.flush();
                
                Student stu2 
= new Student();
                session.save(stu2);
//没有设置name字段,预期会报出例外
                   session.flush();
                session.connection().commit();
                
//至于session的关闭就不用我们操心了
                return null;
            }

        }
);

    }
运行上述代码,没问题了。至此,可能有些读者早就对代码1不满意了:为什么每次save()以后要调用flush()?这是有原因的。下面我们来看看把session.flush()去掉后会出什么问题。改掉后的代码如下:
public static void main(String ss[]) {
        CtxUtil.getBaseManager().getHibernateTemplate().execute(
new HibernateCallback() {
            
public Object doInHibernate(Session session) throws HibernateException, SQLException {
                session.connection().setAutoCommit(
false);
                
// 保存stu1
                Student stu1 = new Student();
                stu1.setName(
"aaaa");// 在数据库中,name字段不允许为null
                session.save(stu1);
                
// session.flush();

                Student stu2 
= new Student();
                session.save(stu2);
// 没有设置name字段,预期会报出例外
                
// session.flush();
                session.connection().commit();
                
return null;
            }

        }
);

    }
运行上述代码,后台报数据库的not null错误,这个是合理的,打开数据库,没有发现新增记录,这个也是合理的。你可能会说:由于事务失败,数据库当然不可能会有任何新增记录。好吧,我们再把代码改一下,去除not null的错误,以确保它能正常运行。代码如下:
public static void main(String ss[]) {
        CtxUtil.getBaseManager().getHibernateTemplate().execute(
new HibernateCallback() {
            
public Object doInHibernate(Session session) throws HibernateException, SQLException {
                session.connection().setAutoCommit(
false);
                
// 保存stu1
                Student stu1 = new Student();
                stu1.setName(
"aaaa");// 在数据库中,name字段不允许为null
                session.save(stu1);
                
// session.flush();

                Student stu2 
= new Student();
                stu2.setName(
"asdfasdf");//好了,这个字段设过值,不会再报not null错误了
                session.save(stu2);
                
// session.flush();
                session.connection().commit();
                
return null;
            }

        }
);

    }

至此再运行上述代码,出现了一个奇怪的问题:
虽然控制台把insert语句打出来了,但是:数据库没有出现任何新的记录。
究其原因,有二:
一. session.connection().commit()确实导致数据库事务提交了,但是此刻session并没有向数据库发送任何语句。
二.在spring后继的flushIfNecessary()和closeSessionOrRegisterDeferredClose()方法中,第一个方法向数据库发送sql语句,第二个方法关闭session,同时关闭connection,然后问题在于:connection已经在程序中被手动设置为auttocommit=false了,因此在关闭数据库时,也不会提交事务。
解决这个问题很容易,在程序中手动调用session.flush()就可以了。如下代码:
public static void main(String ss[]) {
        CtxUtil.getBaseManager().getHibernateTemplate().execute(
new HibernateCallback() {
            
public Object doInHibernate(Session session) throws HibernateException, SQLException {
                session.connection().setAutoCommit(
false);
                
                
//保存stu1
                Student stu1=new Student();
                stu1.setName(
"aaaa");//在数据库中,name字段不允许为null
                session.save(stu1);
                
                Student stu2 
= new Student();
                session.save(stu2);
//没有设置name字段,预期会报出例外
                
                session.flush();//向数据库发送sql
                session.connection().commit();
                
return null;
            }

        }
);

    }

运行上述代码,打开数据库查看,没有新增任何记录。在代码中新加一行stu2.setName("aaa");再次运行代码,发现数据库表中多了两条记录。事务操作成功。
至此,虽然操作成功,但事情还没有结束。这是因为spring在调用doInHibernate()的后继的步骤中,还要进行flushIfNecessary()操作,这个操作其实最后调用的还是session.flush()。因为在程序中已经手动调用过session.flush(),所以由spring调用的session.flush()并不会对数据库发送sql(因为脏数据比对的原因)。虽然不会对结果有什么影响,但是多调了一次flush(),还是会对性能多少有些影响。能不能控制让spring不调用session.flush()呢?可以的,只要加上一句代码,如下所示:
public static void main(String ss[]) {
        CtxUtil.getBaseManager().getHibernateTemplate().setFlushMode(
0);//0也就是FLUSH_NEVER
        CtxUtil.getBaseManager().getHibernateTemplate().execute(new HibernateCallback() {
            
public Object doInHibernate(Session session) throws HibernateException, SQLException {
                session.connection().setAutoCommit(
false);
                
                
//保存stu1
                Student stu1=new Student();
                stu1.setName(
"aaaa");//在数据库中,name字段不允许为null
                session.save(stu1);
                
                Student stu2 
= new Student();
                stu2.setName(
"sdf");
                session.save(stu2);
//没有设置name字段,预期会报出例外
                
                session.flush();
                session.connection().commit();
                
return null;
            }

        }
);

    }
通过设置HibernateTemplate的flushMode=FLUSH_NEVER来通知spring不进行session.flush()的调用,则spring的flushIfNecessary()将不进行任何操作,它的flushIfNecessary()源代码如下:
protected void flushIfNecessary(Session session, boolean existingTransaction) throws HibernateException {
        
if (getFlushMode() == FLUSH_EAGER || (!existingTransaction && getFlushMode() != FLUSH_NEVER)) {
            logger.debug(
"Eagerly flushing Hibernate session");
            session.flush();
        }

    }
至此,代码1中的main()终于修改完毕。但事实上,这样的操作无疑是比较麻烦的,因此如果在spring中想利用session进行事务操作时,最好还是用TransactionTemplate(编程式事务)或是声明式事务比较方便一些。
本例通过这么一个虽然简单但又绕来绕去的例子,主要是说明hibernate事务的一些内在特性,以及HibernateTemplate中如何处理session和事务的开关,让读者对HibernateTemplate的源代码处理细节有一些了解,希望能给读者有抛砖引玉的作用。

posted on 2007-04-25 13:42 caixuetao 阅读(25366) 评论(9)  编辑  收藏

评论

# re: HibernateTemplate中HibernateCallback的事务  回复  更多评论   

好文啊,透彻!
2007-04-26 15:24 | ztroma

# re: HibernateTemplate中HibernateCallback的事务  回复  更多评论   

最近在研究您的大作,学习hibernate,希望多多指导,不懂之处我会给您发邮件,有空能回复邮件,万分感谢jb198388@163.com
2007-04-30 02:56 | sleepingboy

# re: HibernateTemplate中HibernateCallback的事务  回复  更多评论   

HibernateTemplate确实没有设置session的事务属性,因为在spring的模型中,事务本来就不是HibernateTemplate来管理的。HibernateTemplate的作用是保证session能够正确的打开和关闭,避免手工管理session带来的问题,同时让session自动的参与事务,转换检查异常。spring中的事务应该使用TransactionTemplate,或者是是更加好的,类似EJB的声明式事务。如果配置了声明式事务,HibernateTemplate就可以保证让session自动参与事务,这一点从HibernateTemplate的源代码中可以看出来。手工管理的session在多线程操作,声明式事务的各种场景下,会造成代码的混乱。不推荐手工管理session,也不推荐手工管理事务。最佳的实践,是HibernateTemplate + 声明式事务。楼主没有使用任何spring的事务机制,所以HibernateTemplate确实就是 无事务,这就是autoCommit==true的原因。楼主的例子在实践中极为危险。
2007-07-07 13:44 | www

# re: HibernateTemplate中HibernateCallback的事务  回复  更多评论   

"spring管理范围内的session所持有的connection是autocommit=true的"

这个能否提供依据?谢谢了
2008-06-29 13:08 | thebye85

# re: HibernateTemplate中HibernateCallback的事务  回复  更多评论   

正在看蔡老师的Hibernate开发及其整合应用大全,但是在图书馆借的书都没源代码,请问蔡老师可以给我一份吗?我的邮箱是:hank31713@163.com,谢谢了
2008-07-24 22:21 | 易海建

# re: HibernateTemplate中HibernateCallback的事务[未登录]  回复  更多评论   

真是高人啊,解决了一个我的问题关于在回调函数中,session是否自动关闭
2009-09-23 17:36 | 菜鸟

# re: HibernateTemplate中HibernateCallback的事务[未登录]  回复  更多评论   

public static void main(String ss[]) {
CtxUtil.getBaseManager().getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
session.connection().setAutoCommit(false);
// 保存stu1
Student stu1 = new Student();
stu1.setName("aaaa");// 在数据库中,name字段不允许为null
session.save(stu1);
// session.flush();

Student stu2 = new Student();
stu2.setName("asdfasdf");//好了,这个字段设过值,不会再报not null错误了
session.save(stu2);
// session.flush();
session.connection().commit();
return null;
}
});

}


session.connection().setAutoCommit(false); 有这一句当然不能提交,不要这一句呢?
2011-09-11 23:48 | 过客

# re: HibernateTemplate中HibernateCallback的事务[未登录]  回复  更多评论   

怎样获得session?
2014-08-18 00:23 | aa

# re: HibernateTemplate中HibernateCallback的事务  回复  更多评论   

不错 看了三个礼拜,终于看懂了!~~深度解析
2015-11-11 16:01 | 和大象掰腕子

只有注册用户登录后才能发表评论。


网站导航: