Spring中GetTemplate使用中,事务控制没有在Session的生命周期内起作用,而是中途提交了事务

来源:互联网 发布:淘宝店铺标志图片大小 编辑:程序博客网 时间:2024/05/22 00:45
目的:使用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;
  }
  });
  }


我们真正的想法是在session被关闭的时候再进行事务提交,但是这段代码却在flush的时候就进行了提交,为什么?如何避免?
我们在学习Hibenate的时候,事务是在beginTransaction和commit之间进行的,手动开启事务关闭事务的话,完全可以保证你的
事务,目前却不行,所以依照之前的逻辑查看源码
  查看源码:
public Transaction beginTransaction() throws HibernateException
  public Transaction getTransaction() throws HibernateException {
  public Transaction createTransaction(JDBCContext jdbcContext, Context transactionContext)
  public void begin() throws HibernateException
关键代码:jdbcContext.connection().setAutoCommit(false);//把自动提交设为了false
在使用Hibernate时,要在事务结束的时候,写上一句:tx.commit(),这个commit()的源码为:
  public void commit() throws HibernateException 
      commitAndResetAutoCommit();//重点代码,它的作用是提交事务,并把connection的autocommit属性恢复为true



   但是,如果你用的是HibernateTemplate,spring中得到session的方式如下:
Session session = (entityInterceptor != null ? sessionFactory.openSession(entityInterceptor):sessionFactory。openSession());
简单地说它就是得到了一个session,而没有对connection的 autocommit()作任何操作,spring管理范围内的session所持有的connection是autocommit=true 的,spring借助这个属性,在它关
闭session时,提交数据库事务。
因此原因已经找到,我们之前的代码在进行flush的时候,实际上已经提交过一次事务,导致我们不能在session关闭的时候提交,而是中途提交一次,关闭提交一次,违背了我们初衷。

验证:  
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。(log.info(session.connection().getAutoCommit());)
解决:  
  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;
  }
  });
  }


延伸的一个知识:
属性设置的原因:getHibernateTemplate().setFlushMode(0);//0也就是FLUSH_NEVER
实验结果:
  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的源代码处理细节有一些了解,
希望能给读者有抛砖引玉的作用。


参考文章:http://www.cnblogs.com/phoebus0501/archive/2011/01/04/1925757.html
0 0