<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

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

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


    網站導航:
     
    主站蜘蛛池模板: 亚洲精品无码鲁网中文电影| 亚洲不卡视频在线观看| 久久伊人免费视频| 亚洲人成免费电影| 免费一级做a爰片性色毛片| a级毛片视频免费观看| 亚洲免费电影网站| 亚洲av无码不卡私人影院| 国产精品免费大片| 亚洲精品国产首次亮相| 亚洲日产无码中文字幕| 成年网站免费视频A在线双飞| 国产综合成人亚洲区| 久久久久亚洲AV无码专区体验| 成人免费a级毛片无码网站入口| 一级毛片**免费看试看20分钟| 亚洲精品免费在线视频| 亚洲av无码不卡私人影院| 97碰公开在线观看免费视频| 免费中文字幕视频| 亚洲国产日韩综合久久精品| 亚洲综合一区二区| 亚洲国产中文v高清在线观看| 亚洲视频在线免费播放| 精品国产污污免费网站入口| 亚洲中文无码亚洲人成影院| 亚洲AV无码专区国产乱码电影| 日本免费一区二区三区最新vr| 无码国产精品一区二区免费vr| 免费无遮挡无码视频在线观看| 亚洲婷婷综合色高清在线| 久久亚洲精品视频| 亚洲精品人成无码中文毛片| 波多野结衣免费在线观看| 热99RE久久精品这里都是精品免费 | 久久毛片免费看一区二区三区| 伊人久久五月丁香综合中文亚洲| 亚洲AV永久青草无码精品| 亚洲日韩在线观看免费视频| 妞干网在线免费视频| 无码永久免费AV网站|