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 閱讀(25394) 評論(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 | 和大象掰腕子

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 临夏市| 中方县| 洮南市| 山阳县| 商洛市| 九江市| 华阴市| 定日县| 大安市| 高平市| 荃湾区| 亚东县| 黄石市| 绥化市| 环江| 桂东县| 永新县| 灵台县| 五指山市| 德化县| 平陆县| 开化县| 大方县| 琼中| 靖宇县| 陆丰市| 固原市| 中牟县| 潼南县| 竹北市| 乌兰浩特市| 玛多县| 盈江县| 石棉县| 宜州市| 施甸县| 资中县| 宝兴县| 南陵县| 探索| 荔波县|