Hibernate進行時

          有關(guān)Hibenrate及其相關(guān)工具的主頁
          隨筆 - 0, 文章 - 16, 評論 - 29, 引用 - 0
          數(shù)據(jù)加載中……

          HibernateTemplate中HibernateCallback的事務(wù)

          目的:使用HibernateTemplate執(zhí)行execute(new HibernateCallback())方法,從HibernateCallback中得到session,在此session中做多個操作,并希望這些操作位于同一個事務(wù)中。
                如果你這樣寫(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");// 在數(shù)據(jù)庫中,name字段不允許為null
                          session.save(stu1);
                          session.flush();//實際上,如果不是程序員"手癢"來調(diào)用這個flush(),HibernateTemplate中session的事務(wù)處理還是很方便的

                          Student stu2 
          = new Student();
                          session.save(stu2);
          // 沒有設(shè)置name字段,預(yù)期會報出例外
                          session.flush();
                          
          return null;
                      }

                  }
          );

              }
                你期望spring在執(zhí)行完execute回調(diào)后,在關(guān)閉session的時候提交事務(wù),想法是很好的,但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中,執(zhí)行了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);//把自動提交設(shè)為了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時,要在事務(wù)結(jié)束的時候,寫上一句: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();//重點代碼,它的作用是提交事務(wù),并把connection的autocommit屬性恢復(fù)為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();//這段的作用是恢復(fù)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,并手動開啟事務(wù)關(guān)閉事務(wù)的話,完全可以保證你的事務(wù)(好像完全是廢話).
                但是,如果你用的是HibernateTemplate,如同源代碼1一樣,則不要指望spring在關(guān)閉session的時候為你提交事務(wù)(罪魁禍首就是在代碼1中調(diào)用了session.flush())。因為在使用代碼1時,spring中得到session的方式如下:
          Session session = (entityInterceptor != null ? sessionFactory.openSession(entityInterceptor) : sessionFactory
                          .openSession());
          簡單地說它就是得到了一個session,而沒有對connection的autocommit()作任何操作,spring管理范圍內(nèi)的session所持有的connection是autocommit=true的,spring借助這個屬性,在它關(guān)閉session時,提交數(shù)據(jù)庫事務(wù)。,因此如果你在源代碼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());
          //打印一下事務(wù)提交方式
                          
          // 保存stu1
                          Student stu1 = new Student();
                          stu1.setName(
          "aaaa");// 在數(shù)據(jù)庫中,name字段不允許為null
                          session.save(stu1);
                          session.flush();

                          Student stu2 
          = new Student();
                          session.save(stu2);
          // 沒有設(shè)置name字段,預(yù)期會報出例外
                          session.flush();
                          
          return null;
                      }

                  }
          );

              }
               運行后,它打出的結(jié)果是true,也就是說,雖然保存stu2時會報出例外,但如果commit屬性為true,則每一個到達數(shù)據(jù)庫的sql語句會立即被提交。換句話說,在調(diào)用完session.save(stu1)后,調(diào)用session.flush(),會發(fā)送sql語句到數(shù)據(jù)庫,再根據(jù)commit屬性為true,則保存stu1的操作已經(jīng)被持久到數(shù)據(jù)庫了,盡管后面的一條insert語句出了問題。
               因此,如果你想在HibernateCallback中使用session的事務(wù),需要如下寫:
          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");//在數(shù)據(jù)庫中,name字段不允許為null
                          session.save(stu1);
                          session.flush();
                          
                          Student stu2 
          = new Student();
                          session.save(stu2);
          //沒有設(shè)置name字段,預(yù)期會報出例外
                             session.flush();
                          session.connection().commit();
                          
          //至于session的關(guān)閉就不用我們操心了
                          return null;
                      }

                  }
          );

              }
          運行上述代碼,沒問題了。至此,可能有些讀者早就對代碼1不滿意了:為什么每次save()以后要調(diào)用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");// 在數(shù)據(jù)庫中,name字段不允許為null
                          session.save(stu1);
                          
          // session.flush();

                          Student stu2 
          = new Student();
                          session.save(stu2);
          // 沒有設(shè)置name字段,預(yù)期會報出例外
                          
          // session.flush();
                          session.connection().commit();
                          
          return null;
                      }

                  }
          );

              }
          運行上述代碼,后臺報數(shù)據(jù)庫的not null錯誤,這個是合理的,打開數(shù)據(jù)庫,沒有發(fā)現(xiàn)新增記錄,這個也是合理的。你可能會說:由于事務(wù)失敗,數(shù)據(jù)庫當然不可能會有任何新增記錄。好吧,我們再把代碼改一下,去除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");// 在數(shù)據(jù)庫中,name字段不允許為null
                          session.save(stu1);
                          
          // session.flush();

                          Student stu2 
          = new Student();
                          stu2.setName(
          "asdfasdf");//好了,這個字段設(shè)過值,不會再報not null錯誤了
                          session.save(stu2);
                          
          // session.flush();
                          session.connection().commit();
                          
          return null;
                      }

                  }
          );

              }

          至此再運行上述代碼,出現(xiàn)了一個奇怪的問題:
          雖然控制臺把insert語句打出來了,但是:數(shù)據(jù)庫沒有出現(xiàn)任何新的記錄。
          究其原因,有二:
          一. session.connection().commit()確實導(dǎo)致數(shù)據(jù)庫事務(wù)提交了,但是此刻session并沒有向數(shù)據(jù)庫發(fā)送任何語句。
          二.在spring后繼的flushIfNecessary()和closeSessionOrRegisterDeferredClose()方法中,第一個方法向數(shù)據(jù)庫發(fā)送sql語句,第二個方法關(guān)閉session,同時關(guān)閉connection,然后問題在于:connection已經(jīng)在程序中被手動設(shè)置為auttocommit=false了,因此在關(guān)閉數(shù)據(jù)庫時,也不會提交事務(wù)。
          解決這個問題很容易,在程序中手動調(diào)用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");//在數(shù)據(jù)庫中,name字段不允許為null
                          session.save(stu1);
                          
                          Student stu2 
          = new Student();
                          session.save(stu2);
          //沒有設(shè)置name字段,預(yù)期會報出例外
                          
                          session.flush();//向數(shù)據(jù)庫發(fā)送sql
                          session.connection().commit();
                          
          return null;
                      }

                  }
          );

              }

          運行上述代碼,打開數(shù)據(jù)庫查看,沒有新增任何記錄。在代碼中新加一行stu2.setName("aaa");再次運行代碼,發(fā)現(xiàn)數(shù)據(jù)庫表中多了兩條記錄。事務(wù)操作成功。
          至此,雖然操作成功,但事情還沒有結(jié)束。這是因為spring在調(diào)用doInHibernate()的后繼的步驟中,還要進行flushIfNecessary()操作,這個操作其實最后調(diào)用的還是session.flush()。因為在程序中已經(jīng)手動調(diào)用過session.flush(),所以由spring調(diào)用的session.flush()并不會對數(shù)據(jù)庫發(fā)送sql(因為臟數(shù)據(jù)比對的原因)。雖然不會對結(jié)果有什么影響,但是多調(diào)了一次flush(),還是會對性能多少有些影響。能不能控制讓spring不調(diào)用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");//在數(shù)據(jù)庫中,name字段不允許為null
                          session.save(stu1);
                          
                          Student stu2 
          = new Student();
                          stu2.setName(
          "sdf");
                          session.save(stu2);
          //沒有設(shè)置name字段,預(yù)期會報出例外
                          
                          session.flush();
                          session.connection().commit();
                          
          return null;
                      }

                  }
          );

              }
          通過設(shè)置HibernateTemplate的flushMode=FLUSH_NEVER來通知spring不進行session.flush()的調(diào)用,則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進行事務(wù)操作時,最好還是用TransactionTemplate(編程式事務(wù))或是聲明式事務(wù)比較方便一些。
          本例通過這么一個雖然簡單但又繞來繞去的例子,主要是說明hibernate事務(wù)的一些內(nèi)在特性,以及HibernateTemplate中如何處理session和事務(wù)的開關(guān),讓讀者對HibernateTemplate的源代碼處理細節(jié)有一些了解,希望能給讀者有拋磚引玉的作用。

          posted on 2007-04-25 13:42 caixuetao 閱讀(25395) 評論(9)  編輯  收藏

          評論

          # re: HibernateTemplate中HibernateCallback的事務(wù)  回復(fù)  更多評論   

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

          # re: HibernateTemplate中HibernateCallback的事務(wù)  回復(fù)  更多評論   

          最近在研究您的大作,學(xué)習(xí)hibernate,希望多多指導(dǎo),不懂之處我會給您發(fā)郵件,有空能回復(fù)郵件,萬分感謝jb198388@163.com
          2007-04-30 02:56 | sleepingboy

          # re: HibernateTemplate中HibernateCallback的事務(wù)  回復(fù)  更多評論   

          HibernateTemplate確實沒有設(shè)置session的事務(wù)屬性,因為在spring的模型中,事務(wù)本來就不是HibernateTemplate來管理的。HibernateTemplate的作用是保證session能夠正確的打開和關(guān)閉,避免手工管理session帶來的問題,同時讓session自動的參與事務(wù),轉(zhuǎn)換檢查異常。spring中的事務(wù)應(yīng)該使用TransactionTemplate,或者是是更加好的,類似EJB的聲明式事務(wù)。如果配置了聲明式事務(wù),HibernateTemplate就可以保證讓session自動參與事務(wù),這一點從HibernateTemplate的源代碼中可以看出來。手工管理的session在多線程操作,聲明式事務(wù)的各種場景下,會造成代碼的混亂。不推薦手工管理session,也不推薦手工管理事務(wù)。最佳的實踐,是HibernateTemplate + 聲明式事務(wù)。樓主沒有使用任何spring的事務(wù)機制,所以HibernateTemplate確實就是 無事務(wù),這就是autoCommit==true的原因。樓主的例子在實踐中極為危險。
          2007-07-07 13:44 | www

          # re: HibernateTemplate中HibernateCallback的事務(wù)  回復(fù)  更多評論   

          "spring管理范圍內(nèi)的session所持有的connection是autocommit=true的"

          這個能否提供依據(jù)?謝謝了
          2008-06-29 13:08 | thebye85

          # re: HibernateTemplate中HibernateCallback的事務(wù)  回復(fù)  更多評論   

          正在看蔡老師的Hibernate開發(fā)及其整合應(yīng)用大全,但是在圖書館借的書都沒源代碼,請問蔡老師可以給我一份嗎?我的郵箱是:hank31713@163.com,謝謝了
          2008-07-24 22:21 | 易海建

          # re: HibernateTemplate中HibernateCallback的事務(wù)[未登錄]  回復(fù)  更多評論   

          真是高人啊,解決了一個我的問題關(guān)于在回調(diào)函數(shù)中,session是否自動關(guān)閉
          2009-09-23 17:36 | 菜鳥

          # re: HibernateTemplate中HibernateCallback的事務(wù)[未登錄]  回復(fù)  更多評論   

          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");// 在數(shù)據(jù)庫中,name字段不允許為null
          session.save(stu1);
          // session.flush();

          Student stu2 = new Student();
          stu2.setName("asdfasdf");//好了,這個字段設(shè)過值,不會再報not null錯誤了
          session.save(stu2);
          // session.flush();
          session.connection().commit();
          return null;
          }
          });

          }


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

          # re: HibernateTemplate中HibernateCallback的事務(wù)[未登錄]  回復(fù)  更多評論   

          怎樣獲得session?
          2014-08-18 00:23 | aa

          # re: HibernateTemplate中HibernateCallback的事務(wù)  回復(fù)  更多評論   

          不錯 看了三個禮拜,終于看懂了!~~深度解析
          2015-11-11 16:01 | 和大象掰腕子

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


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 九龙坡区| 蒙城县| 明光市| 宝应县| 叶城县| 四会市| 聂拉木县| 台东县| 漳平市| 祁门县| 阳谷县| 铜山县| 伊春市| 灵丘县| 广河县| 长葛市| 天峨县| 双城市| 保山市| 古田县| 赤水市| 文化| 吉首市| 海城市| 莒南县| 通渭县| 司法| 内丘县| 鄄城县| 城步| 河北区| 玉龙| 崇仁县| 大渡口区| 瓦房店市| 泊头市| 贵阳市| 平原县| 阳城县| 达孜县| 长寿区|