問題:客戶在其數據庫操作過程中需要調用我們的工作流接口,這樣就需要將我們的工作流操作與他 們的業 務操作置於同一個事務中。我們的服務采用的都是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;
}
});
}