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(true, this); } notifyLocalSynchsAfterTransactionCompletion(Status.STATUS_COMMITTED); } catch (SQLException e) { log.error("JDBC commit failed", e); commitFailed = true; if (callback) { jdbcContext.afterTransactionCompletion(false, this); } 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; } }); }


通过设置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的源代码处理细节有一些了解,希望能给读者有抛砖引玉的作用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值