zoukankan      html  css  js  c++  java
  • Spring中HibernateTemplate类的使用

      实际情况中,对于编程事务的操作最好还是使用org.springframework.transaction.support.TransactionTemplate,因为HibernateTemplate在实际操作中并不是想象的那样,具体如下:

    目的:使用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;
      }
      });
      }
      至此再运行上述代码,出现了一个奇怪的问题:虽然控制台把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的源代码处理细节有一些了解,
    希望能给读者有抛砖引玉的作用。

  • 相关阅读:
    运算优先级
    (5).plus(3).minus(2);
    画图
    函数的三种角色
    表格排序
    正则表达式
    call apply
    css文本属性和边框属性
    css颜色/字体/背景属性
    css选择器优先级及继承
  • 原文地址:https://www.cnblogs.com/phoebus0501/p/1925757.html
Copyright © 2011-2022 走看看