熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java開源技術 >> 正文

HibernateTemplate 類 使用

2022-06-13   來源: Java開源技術 

  目的使用HibernateTemplate執行execute(new HibernateCallback())方法從HibernateCallback中得到session在此session中做多個操作並希望這些操作位於同一個事務中

  如果你這樣寫(

          public static void main(String ss[]) {
        CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException SQLException {
                // 保存stu
                Student stu = new Student();
                stusetName(aaaa);// 在數據庫中name字段不允許為null
                sessionsave(stu);
                sessionflush();//實際上如果不是程序員手癢來調用這個flush()HibernateTemplate中session的事務處理還是很方便的

                Student stu = new Student();
                sessionsave(stu);// 沒有設置name字段預期會報出例外
                sessionflush();
                return null;
            }
        });

    }

  你期望spring在執行完execute回調後在關閉session的時候提交事務想法是很好的但spring並不會這麼做讓我們來看看在Hibernate的源代碼中sessionbeginTransation()做了什麼事看如下代碼(

   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
            logwarn( Transaction started on nonroot session );
        }
        Transaction result = getTransaction();
        resultbegin();
        return result;
    }

  這個方法中的result是一個orghibernatetransactionJDBCTransaction實例而方法中的getTransaction()方法源代碼為(

   public Transaction getTransaction() throws HibernateException {
        if (hibernateTransaction==null) {
            logerror(ownergetFactory()getSettings()
                    getTransactionFactory()getClass());
            hibernateTransaction = ownergetFactory()getSettings()
                    getTransactionFactory()
                    createTransaction( this owner );
        }
        return hibernateTransaction;
    }

  再次追蹤ownergetFactory()getSettings() getTransactionFactory()的createTransaction()方法源代碼如下(

   public Transaction createTransaction(JDBCContext jdbcContext Context transactionContext)
    throws HibernateException {
        return new JDBCTransaction( jdbcContext transactionContext );
    }

  它返回了一個JDBCTransaction沒什麼特別的

  在代碼執行了resultbegin()其實也就是JDBCTransaction實例的begin()方法來看看(

   public void begin() throws HibernateException {
        if (begun) {
            return;
        }
        if (commitFailed) {
            throw new TransactionException(cannot restart transaction after failed commit);
        }
        logdebug(begin);
        try {
            toggleAutoCommit = nnection()getAutoCommit();
            if (logisDebugEnabled()) {
                logdebug(current autocommit status: + toggleAutoCommit);
            }
            if (toggleAutoCommit) {
                logdebug(disabling autocommit);
                nnection()setAutoCommit(false);//把自動提交設為了false
            }
        } catch (SQLException e) {
            logerror(JDBC begin failed e);
            throw new TransactionException(JDBC begin failed: e);
        }
        callback = jdbcContextregisterCallbackIfNecessary();
        begun = true;
        committed = false;
        rolledBack = false;

        if (timeout > ) {
            jdbcContextgetConnectionManager()getBatcher()setTransactionTimeout(timeout);
        }

        jdbcContextafterTransactionBegin(this);
    }

  在直接使用Hibernate時要在事務結束的時候寫上一句mit()這個commit()的源碼為

   public void commit() throws HibernateException {
        if (!begun) {
            throw new TransactionException(Transaction not successfully started);
        }

        logdebug(commit);

        if (!transactionContextisFlushModeNever() && callback) {
            transactionContextmanagedFlush(); // if an exception occurs during
            // flush user must call
            // rollback()
        }

        notifyLocalSynchsBeforeTransactionCompletion();
        if (callback) {
            jdbcContextbeforeTransactionCompletion(this);
        }

        try {
            commitAndResetAutoCommit();//重點代碼它的作用是提交事務並把connection的autocommit屬性恢復為true
            logdebug(committed JDBC Connection);
            committed = true;
            if (callback) {
                jdbcContextafterTransactionCompletion(true this);
            }
            notifyLocalSynchsAfterTransactionCompletion(StatusSTATUS_COMMITTED);
        } catch (SQLException e) {
            logerror(JDBC commit failed e);
            commitFailed = true;
            if (callback) {
                jdbcContextafterTransactionCompletion(false this);
            }
            notifyLocalSynchsAfterTransactionCompletion(StatusSTATUS_UNKNOWN);
            throw new TransactionException(JDBC commit failed e);
        } finally {
            closeIfRequired();
        }
    }

  上面代碼中commitAndResetAutoCommit()方法的源碼如下

   private void commitAndResetAutoCommit() throws SQLException {
        try {
            nnection(mit();//這段不用說也能理解了
        } finally {
            toggleAutoCommit();//這段的作用是恢復connection的autocommit屬性為true
        }
    }

  上述代碼的toggleAutoCommit()源代碼如下

       private void toggleAutoCommit() {
        try {
            if (toggleAutoCommit) {
                logdebug(reenabling autocommit);
                nnection()setAutoCommit(true);//這行代碼的意義很明白了吧
            }
        } catch (Exception sqle) {
            logerror(Could not toggle autocommit sqle);
        }
    }

  因此如果你是直接使用hibernate並手動管理它的session並手動開啟事務關閉事務的話完全可以保證你的事務(好像完全是廢話)

  但是如果你用的是HibernateTemplate如同源代碼一樣則不要指望spring在關閉session的時候為你提交事務(罪魁禍首就是在代碼中調用了sessionflush())因為在使用代碼spring中得到session的方式如下Session session = (entityInterceptor != null ? sessionFactoryopenSession(entityInterceptor) sessionFactoryopenSession())簡單地說它就是得到了一個session而沒有對connection的autocommit()作任何操作spring管理范圍內的session所持有的connection是autocommit=true的spring借助這個屬性在它關閉session時提交數據庫事務因此如果你在源代碼中加上一句話

   public static void main(String ss[]) {
        CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException SQLException {
                (nnection()getAutoCommit());//打印一下事務提交方式
                // 保存stu
                Student stu = new Student();
                stusetName(aaaa);// 在數據庫中name字段不允許為null
                sessionsave(stu);
                sessionflush();

                Student stu = new Student();
                sessionsave(stu);// 沒有設置name字段預期會報出例外
                sessionflush();
                return null;
            }
        });

    }

  運行後它打出的結果是true也就是說雖然保存stu時會報出例外但如果commit屬性為true則每一個到達數據庫的sql語句會立即被提交換句話說在調用完sessionsave(stu)後調用sessionflush()會發送sql語句到數據庫再根據commit屬性為true則保存stu的操作已經被持久到數據庫了盡管後面的一條insert語句出了問題

  因此如果你想在HibernateCallback中使用session的事務需要如下寫

   public static void main(String ss[]) {
        CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException SQLException {
                nnection()setAutoCommit(false);
                //保存stu
                Student stu=new Student();
                stusetName(aaaa);//在數據庫中name字段不允許為null
                sessionsave(stu);
                sessionflush();
                
                Student stu = new Student();
                sessionsave(stu);//沒有設置name字段預期會報出例外
                   sessionflush();
                nnection(mit();
                //至於session的關閉就不用我們操心了
                return null;
            }
        });

    }

  運行上述代碼沒問題了至此可能有些讀者早就對代碼不滿意了為什麼每次save()以後要調用flush()?這是有原因的下面我們來看看把sessionflush()去掉後會出什麼問題改掉後的代碼如下

   public static void main(String ss[]) {
        CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException SQLException {
                nnection()setAutoCommit(false);
                // 保存stu
                Student stu = new Student();
                stusetName(aaaa);// 在數據庫中name字段不允許為null
                sessionsave(stu);
                // sessionflush();

                Student stu = new Student();
                sessionsave(stu);// 沒有設置name字段預期會報出例外
                // sessionflush();
                nnection(mit();
                return null;
            }
        });

    }

  運行上述代碼後台報數據庫的not null錯誤這個是合理的打開數據庫沒有發現新增記錄這個也是合理的你可能會說由於事務失敗數據庫當然不可能會有任何新增記錄好吧我們再把代碼改一下去除not null的錯誤以確保它能正常運行代碼如下

   public static void main(String ss[]) {
        CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException SQLException {
                nnection()setAutoCommit(false);
                // 保存stu
                Student stu = new Student();
                stusetName(aaaa);// 在數據庫中name字段不允許為null
                sessionsave(stu);
                // sessionflush();

                Student stu = new Student();
                stusetName(asdfasdf);//好了這個字段設過值不會再報not null錯誤了
                sessionsave(stu);
                // sessionflush();
                nnection(mit();
                return null;
            }
        });

    }

  至此再運行上述代碼出現了一個奇怪的問題雖然控制台把insert語句打出來了但是數據庫沒有出現任何新的記錄

  究其原因有二

  一 nnection()commit()確實導致數據庫事務提交了但是此刻session並沒有向數據庫發送任何語句

  二 在spring後繼的flushIfNecessary()和closeSessionOrRegisterDeferredClose()方法中第一個方法向數據庫發送sql語句第二個方法關閉session同時關閉connection然後問題在於connection已經在程序中被手動設置為auttocommit=false了因此在關閉數據庫時也不會提交事務

  解決這個問題很容易在程序中手動調用sessionflush()就可以了如下代碼

   public static void main(String ss[]) {
        CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException SQLException {
                nnection()setAutoCommit(false);
                
                //保存stu
                Student stu=new Student();
                stusetName(aaaa);//在數據庫中name字段不允許為null
                sessionsave(stu);
                
                Student stu = new Student();
                sessionsave(stu);//沒有設置name字段預期會報出例外
                
                sessionflush();//向數據庫發送sql
                nnection(mit();
                return null;
            }
        });

    }

  運行上述代碼打開數據庫查看沒有新增任何記錄在代碼中新加一行stusetName(aaa再次運行代碼發現數據庫表中多了兩條記錄事務操作成功

  至此雖然操作成功但事情還沒有結束這是因為spring在調用doInHibernate()的後繼的步驟中還要進行flushIfNecessary()操作這個操作其實最後調用的還是sessionflush()因為在程序中已經手動調用過sessionflush()所以由spring調用的sessionflush()並不會對數據庫發送sql(因為髒數據比對的原因)雖然不會對結果有什麼影響但是多調了一次flush()還是會對性能多少有些影響能不能控制讓spring不調用sessionflush()呢?可以的只要加上一句代碼如下所示

   public static void main(String ss[]) {
        CtxUtilgetBaseManager()getHibernateTemplate()setFlushMode();//也就是FLUSH_NEVER
        CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException SQLException {
                nnection()setAutoCommit(false);
                
                //保存stu
                Student stu=new Student();
                stusetName(aaaa);//在數據庫中name字段不允許為null
                sessionsave(stu);
                
                Student stu = new Student();
                stusetName(sdf);
                sessionsave(stu);//沒有設置name字段預期會報出例外
                
                sessionflush();
                nnection(mit();
                return null;
            }
        });

    }

  通過設置HibernateTemplate的flushMode=FLUSH_NEVER來通知spring不進行sessionflush()的調用則spring的flushIfNecessary()將不進行任何操作它的flushIfNecessary()源代碼如下

   protected void flushIfNecessary(Session session boolean existingTransaction) throws HibernateException {
        if (getFlushMode() == FLUSH_EAGER || (!existingTransaction && getFlushMode() != FLUSH_NEVER)) {
            loggerdebug(Eagerly flushing Hibernate session);
            sessionflush();
        }
    }

  至此代碼中的main()終於修改完畢但事實上這樣的操作無疑是比較麻煩的因此如果在spring中想利用session進行事務操作時最好還是用TransactionTemplate(編程式事務)或是聲明式事務比較方便一些

  本例通過這麼一個雖然簡單但又繞來繞去的例子主要是說明hibernate事務的一些內在特性以及HibernateTemplate中如何處理session和事務的開關讓讀者對HibernateTemplate的源代碼處理細節有一些了解希望能給讀者有拋磚引玉的作用


From:http://tw.wingwit.com/Article/program/Java/ky/201311/27857.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.