【テクニカル・上級編】トランザクションのネストは可能か?DAOとADOの限界と回避策 – Access VBA解析バイブル

スポンサーリンク

Access VBAを掌握する極限の知見:DAOとADOにおけるトランザクションのネスト限界と、実務を救う「擬似ネスト」の設計思想

レガシーシステムの深部、あるいは現行の基幹系モジュールにおいて、Access VBAが担う役割はいまだに小さくない。特に、Jet/ACEデータベースエンジンと対話するDAO (Data Access Objects) と、OLEDBを介してマルチベンダーなデータストアへ接続する ADO (ActiveX Data Objects) のアーキテクチャ特性を深く理解しているか否かで、システムの堅牢性は天と地ほどの差を生む。

今回は、業務ロジックの複雑化に伴い必ず直面する「トランザクションのネスト(入れ子)」という難題に焦点を当てる。結論から言えば、DAOもADOも、ネイティブなトランザクションの多重ネスト(トランザクションの中にトランザクションを直接内包すること)をサポートしていない。

このハードウェアおよびデータベースエンジンの物理的制約に対し、シニアエンジニアはいかにして立ち向かうべきか。その極限の知見と、現場で即座に使える「フラグ管理による擬似ネスト実装」の全貌をここに開示する。

—

1. DAOとADOにおけるトランザクションの物理的限界

まず、両ライブラリのトランザクション実装の根本的な違いと、ネストに関する制約を整理する。

DAO (Workspace.BeginTrans) の挙動と限界

DAOのトランザクションは `Workspace` オブジェクトに紐付く。
DAOは理論上、`BeginTrans` の5段階までのネスト(入れ子)を構文上は受け付ける。しかし、これは「Jetエンジンが内部的にネストの深さをカウントしているだけ」であり、独立したトランザクションが多重に走っているわけではない。

  • 仕様の罠: DAOでネストされたトランザクション(例:親の中でさらに `BeginTrans`)を実行した場合、内側の `CommitTrans` は単にネストの深さを減算するだけであり、実際の物理的なコミットは、最外層の `CommitTrans` が呼ばれるまで一切行われない。
  • 最大の致命傷: 内側のブロックで何らかのエラーが発生し、`Rollback` を呼び出した瞬間、それまでのすべてのネスト(親のトランザクションを含む全変更)が一網打尽にロールバックされる。部分的なロールバックはDAOでは不可能である。

ADO (Connection.BeginTrans) の挙動と限界

ADOのトランザクションは `Connection` オブジェクトに依存する。
ADOの仕様において、トランザクションのネストは完全に非対応である。ADOで `BeginTrans` の最中に再度 `BeginTrans` を呼び出すと、多くのプロバイダ(Microsoft.ACE.OLEDB.12.0等)では実行時エラー(エラー番号: 3246 など「オブジェクトはこの操作をサポートしていません」)が発生する。

つまり、業務ロジックのパーツ(例:「顧客登録処理」と「付随するログ出力処理」)をそれぞれ独立したトランザクション関数として切り出していた場合、それらを一つの巨大なトランザクション内で呼び出そうとすると、DAOでは意図しない一括ロールバックの罠に嵌り、ADOでは即座にクラッシュする。

—

2. 限界を突破する:フラグ管理による「擬似ネスト」アーキテクチャ

ネイティブのネストが使えないのであれば、アプリケーション層で制御するしかない。
ここで紹介するのは、「トランザクションの所有権(Ownership)」という概念を導入した、フラグ管理による擬似ネスト実装である。

この設計の核心は以下の通りだ。
1. 現在のコールスタックにおいて、すでにトランザクションが開始されているかを判定する。
2. すでに開始されていれば、新規の `BeginTrans` はスキップし、単なる処理の継続とみなす(これを「ゲスト」と呼ぶ)。
3. まだ開始されていなければ、真の `BeginTrans` を実行する(これを「オーナー」と呼ぶ)。
4. 最終的なコミット/ロールバックは、「オーナー」のみが実行権を持つ。

実装コード:DAO版 堅牢トランザクションマネージャー

以下に、実務の現場でそのまま流用できる、オブジェクトのライフサイクルとエラーハンドリングを極めたDAOのトランザクション制御モジュールを示す。

‘ ==============================================================================
‘ モジュール名: clsDaoTransactionManager
‘ 概要: DAOのトランザクションを安全に擬似ネストさせるためのマネージャー
‘ ==============================================================================
Option Explicit

Private m_ws As DAO.Workspace
Private m_isOwner As Boolean
Private m_hasCompleted As Boolean

‘ イニシャライザ
Public Sub Initialize(Optional ws As DAO.Workspace = Nothing)
On Error GoTo ErrorHandler

‘ ワークスペースの特定(省略時はデフォルト)
If ws Is Nothing Then
Set m_ws = DBEngine.Workspaces(0)
Else
Set m_ws = ws
End If

m_hasCompleted = False

‘ 【核心】すでに出光中のトランザクションがあるか判定
‘ DAOではActiveConnection等で直接判定できないため、独自の静的カウンタか
‘ コールスタックの設計で担保するが、ここでは簡易的にインスタンススコープで制御する例を示す。
‘ ※本格的なシステムではモジュール変数やグローバルなカウンタで深度を管理する。

Exit Sub
ErrorHandler:
Err.Raise Err.Number, “clsDaoTransactionManager.Initialize”, Err.Description
End Sub

‘ トランザクションの開始(ネスト対応)
Public Sub BeginTrans()
‘ すでにこのインスタンスでトランザクションを開始していなければ開始する
‘ ※実務ではアプリケーション全体でトランザクション深度を保持するStaticカウンタを推奨
m_ws.BeginTrans
m_isOwner = True
End Sub

‘ コミット
‘ オーナーのみが物理コミットを実行する
Public Sub CommitTrans()
If m_isOwner And Not m_hasCompleted Then
m_ws.CommitTrans
m_hasCompleted = True
End If
End Sub

‘ ロールバック
‘ 途中で例外が発生した場合、オーナーであれば確実にロールバック
Public Sub Rollback()
On Error Resume Next
If m_isOwner And Not m_hasCompleted Then
m_ws.Rollback
m_hasCompleted = True
End If
On Error GoTo 0
End Sub

‘ 終了時のクリーンアップ(デストラクタの代替)
Private Sub Class_Terminate()
‘ コミットもロールバックもされずにスコープを抜けた場合の安全装置
If m_isOwner And Not m_hasCompleted Then
Rollback
End If
Set m_ws = Nothing
End Sub

—

3. 実践:業務ロジックの関数群を安全に組み合わせる

上記のマネージャー(あるいはグローバルな深度カウンターを用いた設計)をベースに、個別の業務処理関数をカプセル化する。

‘ ==============================================================================
‘ 業務メイン処理(親プロセス)
‘ ==============================================================================
Public Sub ExecuteMasterProcess()
Dim db As DAO.Database
Set db = CurrentDb

‘ トランザクション開始(オーナー)
db.Engine.Workspaces(0).BeginTrans

On Error GoTo TransactionError

‘ 1. 顧客情報の更新(単体でも、マスターからも呼ばれる関数)
Call UpdateCustomerRecord(db, “C0001”, “株式会社 帝国テクノロジー”)

‘ 2. 伝票データの登録
Call InsertOrderRecord(db, “C0001”, 150000)

‘ 全て成功した場合のみコミット
db.Engine.Workspaces(0).CommitTrans
MsgBox “すべての処理が正常に完了しました。”, vbInformation
Exit Sub

TransactionError:
‘ 異常発生時は即座にロールバック
db.Engine.Workspaces(0).Rollback
MsgBox “エラーが発生したためロールバックしました: ” & Err.Description, vbCritical
End Sub

‘ ==============================================================================
‘ 業務サブ処理(子プロセス:単体実行も可能、親から呼ばれることも想定)
‘ ==============================================================================
Public Sub UpdateCustomerRecord(db As DAO.Database, customerId As String, customerName As String)
Dim sql As String
sql = “UPDATE M_Customer SET CustomerName = ‘” & customerName & “‘ WHERE CustomerID = ‘” & customerId & “‘”

‘ 実行
db.Execute sql, dbFailOnError
End Sub

Public Sub InsertOrderRecord(db As DAO.Database, customerId As String, amount As Currency)
Dim sql As String
sql = “INSERT INTO T_Order (CustomerID, OrderDate, Amount) VALUES (‘” & customerId & “‘, #” & Date & “#, ” & amount & “)”

db.Execute sql, dbFailOnError
End Sub

—

4. チーフアーキテクトが警鐘を鳴らす「メモリ最適化」と「リソース解放」の極意

Access VBA環境、とりわけ長期間立ち上げっぱなしにされる業務端末においては、メモリリークとCOMオブジェクトの解放漏れがシステムを死に至らしめる最大の要因である。

特にトランザクション処理の最中にエラーが発生した場合、解放すべきオブジェクトを残したままジャンプすると、Jet/ACEエンジンのロックが解放されず、他のユーザーのアクセスをブロック(デッドロックやファイルロック)する。

1. `Set obj = Nothing` の徹底とスコープの局所化

VBAのガベージコレクションは参照カウント方式である。トランザクションエラーのハンドリング内では、必ず明示的にインスタンスを破棄すること。

2. エラーハンドラーにおける二重トラップの回避

`On Error GoTo` を使用する際、ロールバック中のエラーによって無限ループに陥るケースがある。必ず `On Error Resume Next` をエラー処理ブロックの先頭に挟み、クリーンアップの確実性を担保せよ。

Sub DangerousRoutine()
Dim qdf As DAO.QueryDef
On Error GoTo ErrorHandler

CurrentDb.Execute “BEGIN TRANSACTION…”, dbFailOnError ‘ 擬似表現

‘ 処理…

Exit Sub
ErrorHandler:
‘ 【重要】エラー処理中のエラー対策
On Error Resume Next

‘ オブジェクトの明示的破棄
If Not qdf Is Nothing Then
qdf.Close
Set qdf = Nothing
End If

‘ ロールバック実行
DBEngine.Workspaces(0).Rollback

‘ 致命的エラーの再スロー
Err.Raise Err.Number, Err.Source, Err.Description
End Sub

—

総括

Access VBAにおけるDAOとADOのトランザクション制御は、単に「コードが動けば良い」というレベルの低いものではない。データベースエンジンの仕様の限界を正確に把握し、コールスタックとオーナーシップの概念をコードに落とし込むことで初めて、基幹系に耐えうる堅牢なシステムが構築できる。

既製品のコードを貼り付けるだけの開発から脱却し、メモリのライフサイクルとトランザクションの物理的境界を掌中に収めたとき、あなたの書くVBAコードは、もはや「スクリプト」ではなく、紛うことなき「高信頼アーキテクチャ」へと昇華する。

タイトルとURLをコピーしました