新、舊系統在一起的交易之路 ~ 取得 Spring.NET 的 Connection 及 Transaction

我們有舊的元件(Workflow),連接DB是透過自行封裝 Ado.NET 的元件,
而到新開發的系統,則是使用 Spring.NET + NHibernate 。
當新、舊交雜在一起使用時,交易該如何控管呢?
最簡單的就是用 TransactionScope 去包起來,
但這時候 MSDTC 就跑起來了。
但是很多單位現在都不給開 MSDTC 了,
那要怎麼辦呢? 取得 DB Connection 及 Transaction 往內傳嗎?
要如何取得 Spring.NET 中的 System.Data.Common.DbTransaction 物件呢?

跟同事Harry討論後,DB Connection 及 Transaction 都由 Spring 來控制,

讓舊元件用的 Connection 也是來自於 Spring 生成的。

那要如何讓舊的元件在取 Connection 時,可以拿到 Spring 生成的 Connection 呢?

這時,我們可以使用 Thread Local Storage 來儲存 Connection 及 Transaction 。

疑~~~ 不是 Connection 就可以了嗎? 為什麼還需要 Transaction 物件呢?

因為 Connection.BeginTransaction 後會傳回一個 Transaction 物件,

而在 Command 執行時,除了需要 Connection 物件外,還需要 Transaction 物件哦!

可以參考「SqlConnection.BeginTransaction 方法 ()」及「SqlCommand.Transaction遇到Exception時,會變成null」。

因為 Connection 在 Spring.NET 中容易取,但是 DbTransaction 要如何取得呢?

在 Spring.NET 起交易後,可以取到 TransactionStatus 物件,

在它的 Transaction 屬性中,有一個非公用成員 connectionHolder ,

connectionHolder 的 Transaction 就是我們要的 DbTransaction ,如下圖,

但如果我們依上圖的Path,透過 Reflection 去找到的,跟本找不到那個 connectionHolder 。

後來跟 Harry 才發現要先將 TransactionStatus.Transaction 轉成 AdoTransactionObjectSupport 物件

而 AdoTransactionObjectSupport 有個 public 屬性就是 ConnectionHolder ,就可以取得 ConnectionHolder.Transaction 物件。

在一開始取得 DbTransaction 的方式如下(SpringHelper 請 Mapping 到您的 Spring.NET 物件),

var transStatus = SpringHelper.InitTransactionManagr(); //取得 Spring.NET 的 TransactionStatus
try
{
	using (SpringHelper.GetScope())
	{
		var adoTxObj = transStatus.Transaction as AdoTransactionObjectSupport;
		var connHolder = adoTxObj.ConnectionHolder;
		
		//先檢查是否已建立
		LocalDataStoreSlot ldsTx = System.Threading.Thread.GetNamedDataSlot("springTx");
		//將 DbTransaction 放到 Thread Local Storage 之中
		System.Threading.Thread.SetData(ldsTx, connectionHolder.Transaction);

		// ... 新、舊交雜在一起使用 ...
		
		//最後沒問題就 Commit 
		SpringHelper.Tx.Commit(transStatus);

	}
}
catch (Exception ex)
{
	//有錯誤就 Rollback 
	SpringHelper.Tx.Rollback(transStatus);
}

 

而舊系統原本 Command 在執行 SQL 之中,除了給它 Connection 外, 如果 LocalDataStoreSlot 有資料的話,就 Assign 給它

var ldsTx = System.Threading.Thread.GetNamedDataSlot("springTx");
var tx = Thread.GetData(ldsTx) as SqlTransaction;
if (tx != null)
	command.Transaction = tx;

 

這種方式有點像是 asp.net 中的 middleware 的方式,在 begin request 時,就將 Connection 及 Transaction 放到 Thread Local Storage 之中,

所以同一個 Thread 中後面執行到需要 Connection 及 Transaction 時,就從 Thread Local Storage 拿出來用就可以了。

 

作者: 亂馬客

亂馬客 @叡揚資訊 rainmaker_ho@gss.com.tw https://rainmakerho.github.io/ https://www.slideshare.net/rainmakerho

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *