問題:客戶在其數據庫操作過程中需要調用我們的工作流接口,這樣就需要將我們的工作流操作與他 們的業 務操作置於同一個事務中。我們的服務采用的都是spring的聲明式事務,而客戶采用的是對 connection進行事務處理。
如何保證JDBC事務的一致性?
想到的解決方案一:使用jta事務,用tomcat+jotm提供事務管理器。為什麼一開始就想到要使用jta事 務??實際上我們和客戶都是使用的同一個數據庫,為了方便,各自使用了不同的數據庫連接方式,使用 jta的話確實有bt的意思在裡面。但是事實上是我們的第一反應都是jta。最後沒有采用該方法的原因也很 簡單:我沒有將jotm配置成功!汗一個。
想到的解決方案二:將客戶的這些特定代碼用spring管理起來。因為要修改客戶部分代碼,這個方案 遭到了客戶的強烈反對。於是放棄。
想到的解決方案三:客戶數據庫操作與我們的服務使用同一個數據庫連接。然後編程處理事務。存在 兩種方式:一種是把客戶的連接傳給我們,另一種則是把我們的連接傳給客戶。第一種方式對我們的影響 太大,所以最後決定采用後一種方式:從hibernate session中獲取connection然後傳遞給客戶。接下來 查看一下HibernateTemplate的execute()方法,思路就很簡單了:獲取定義的sessionFactory-->創建 一個新的session並打開-->將session與當前線程綁定-->給客戶代碼返回connection-->打開事 務-->客戶使用我們傳遞的connection進行數據庫操作-->我們不帶聲明事務的服務操作-->提交 事務-->解除綁定。
JDBC事務實際要注意的地方是:
1、將session與當前線程綁定使用的TransactionSynchronizationManager.bindResource()方法,這 樣在HibernateTemplate裡才能找到session;
2、我們的服務一定要把聲明式事務徹底干掉,否則會有commit;
3、我們服務調用完畢後一定要flush session,否則客戶代碼不會感知數據庫裡的數據變化。
最終解決:使用了spring裡常用的模板和回調。JDBC事務代碼如下:
public class TransactionTemplate { protected final Log logger = LogFactory.getLog(TransactionTemplate.class); private FlushMode flushMode = FlushMode.ALWAYS; public Object execute(TransactionCallback callback) { //首先獲取sessionFactory SessionFactory sessionFactory = (SessionFactory) Framework.getEngine() .getContainer().getComponent("sessionFactory"); //創建一個新的session並打開 logger.debug("Opening single Hibernate Session in TransactionTemplate"); Session session = getSession(sessionFactory); //將session與當前線程綁定 TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); //獲取數據庫連接 Connection conn = session.connection(); Object result = null; Transaction transaction = null; try { //開始處理JDBC事務 transaction = session.beginTransaction(); try { result = callback.doInTransaction(conn); } catch (RuntimeException ex) { doRollback(session, transaction); throw ex; } catch (Error err) { doRollback(session, transaction); throw err; } //如果數據庫操作過程中沒有發生異常則提交事務 transaction.commit(); } catch (WorkflowException e) { logger.error("數據庫操作失敗,事務回滾也失敗!"); throw e; } catch (RuntimeException ex) { logger.error("數據庫操作失敗,事務被回滾!"); throw ex; } catch (Error err) { logger.error("數據庫操作失敗,事務被回滾!"); throw err; } finally { // 將session與當前線程解除綁定 TransactionSynchronizationManager.unbindResource(sessionFactory); doClose(session); } return result; } protected Session getSession(SessionFactory sessionFactory) { Session session = SessionFactoryUtils.getSession(sessionFactory, true); FlushMode flushMode = getFlushMode(); if (flushMode != null) { session.setFlushMode(flushMode); } return session; } private void doRollback(Session session, Transaction transaction) { logger.debug("數據庫操作異常,開始回滾事務"); try { transaction.rollback(); logger.debug("回滾事務成功!"); } catch (Exception e) { logger.error("回滾事務失敗!"); throw new WorkflowException("回滾事務失敗!"); } finally { session.clear(); } } private void doClose(Session session) { logger.debug("開始關閉連接"); try { session.close(); } catch (Exception e) { logger.error("關閉連接失敗!"); throw new WorkflowException("關閉連接失敗!"); } } public FlushMode getFlushMode() { return flushMode; } public void setFlushMode(FlushMode flushMode) { this.flushMode = flushMode; } } public interface TransactionCallback { Object doInTransaction(Connection conn); }
調用偽代碼:
public void methodA(){ TransactionTemplate transactionTemplate=new TransactionTemplate(); transactionTemplate.execute(new TransactionCallback(){ public Object doInTransaction(Connection conn) { //客戶代碼 client.method1("1"); //我們代碼 直接使用 our.method2(); //客戶代碼 client.method3("l"); return null; } }); }