首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Spring+Hibernate 與 JDBC 事務統一:共享 Connection 的模板回調方案

     編輯:Java 相關知識

場景:客戶使用原生 JDBC 事務(Connection 控制),我方服務基於 Spring 聲明式事務(Hibernate Session),雙方操作同一數據庫,需納入同一全局事務。本文提供無需 JTA、無需修改客戶代碼的輕量級整合方案:從 Hibernate Session 提取 Connection 傳給客戶,統一由我方程序化事務管理。

方案演進與排除

  1. JTA (Tomcat+JOTM):理論正確但配置複雜,JOTM 配置失敗,放棄。
  2. Spring 管理客戶代碼:需改動客戶代碼,遭拒。
  3. 共享 Connection(最終方案):我方創建 Session → 綁定線程 → 取 Connection 給客戶 → 開啟事務 → 雙方操作 → 提交/回滾 → 解綁關閉。

核心技術要點

  1. TransactionSynchronizationManager.bindResource(sessionFactory, holder):將 Session 綁定當前線程,讓 HibernateTemplate 能感知到。
  2. 徹底移除服務端聲明式事務:否則雙重提交導致異常。
  3. 服務調用後必須 session.flush():確保 Hibernate 緩存同步到數據庫,客戶端後續 JDBC 操作可見。

完整實現:TransactionTemplate 模板類

public class TransactionTemplate {
    private FlushMode flushMode = FlushMode.ALWAYS;

    public Object execute(TransactionCallback callback) {
        // 1. 獲取 SessionFactory
        SessionFactory sessionFactory = (SessionFactory) Framework.getEngine()
                .getContainer().getComponent("sessionFactory");

        // 2. 創建 Session 並綁定線程
        Session session = getSession(sessionFactory);
        TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));

        // 3. 取得 JDBC Connection 傳給回調
        Connection conn = session.connection();
        Transaction transaction = null;
        Object result = null;

        try {
            // 4. 開啟 JDBC 事務
            transaction = session.beginTransaction();
            try {
                result = callback.doInTransaction(conn); // 執行客戶+我方邏輯
            } catch (RuntimeException | Error ex) {
                doRollback(session, transaction);
                throw ex;
            }
            // 5. 提交事務
            transaction.commit();
        } catch (WorkflowException e) {
            logger.error("數據庫操作失敗,事務回滾也失敗!");
            throw e;
        } catch (RuntimeException | Error ex) {
            logger.error("數據庫操作失敗,事務被回滾!");
            throw ex;
        } finally {
            // 6. 清理:解綁、關閉 Session
            TransactionSynchronizationManager.unbindResource(sessionFactory);
            doClose(session);
        }
        return result;
    }

    protected Session getSession(SessionFactory sf) {
        Session session = SessionFactoryUtils.getSession(sf, true);
        if (flushMode != null) session.setFlushMode(flushMode);
        return session;
    }

    private void doRollback(Session session, Transaction tx) {
        try { tx.rollback(); } catch (Exception e) { throw new WorkflowException("回滾失敗"); }
        finally { session.clear(); }
    }
    private void doClose(Session session) {
        try { session.close(); } catch (Exception e) { throw new WorkflowException("關閉失敗"); }
    }
    // getter/setter...
}

public interface TransactionCallback {
    Object doInTransaction(Connection conn);
}

調用偽代碼

public void methodA() {
    TransactionTemplate template = new TransactionTemplate();
    template.execute(new TransactionCallback() {
        public Object doInTransaction(Connection conn) {
            // 客戶代碼使用傳入的 conn
            client.method1("1");
            // 我方服務無聲明式事務,複用同一 Session/Connection
            ourService.method2();
            client.method3("l");
            return null;
        }
    });
}

關鍵優勢

  • 零侵入客戶代碼,客戶繼續用 JDBC 方式
  • 無需 JTA 容器,輕量級進程內事務
  • 統一事務邊界,ACID 保證
  • 模板模式封裝樣板代碼,業務邏輯清晰

避坑提醒

  • 服務層方法必須移除 @Transactional,否則 Spring 會在方法結束時提交/回滾,破壞統一事務
  • session.flush() 時機:在回調返回前、事務提交前調用,確保 Hibernate 寫入對後續 JDBC 可見
  • 異常處理:回調拋出異常 → 模板捕獲 → 回滾 → 重拋,保持異常鏈完整
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved