データの整合性を守る「防波堤」を築け:DAOトランザクションにおける完璧なエラーハンドリング
業務システムを開発する者にとって、最も恐ろしい瞬間は「エラーでプログラムが止まった時」ではない。「エラーでプログラムが止まった結果、データが中途半端な状態で書き換わっていた時」だ。
在庫テーブルから数量を減らしたが、受注テーブルへの書き込み直前でネットワークが切断された。あるいは、重複エラーが発生して処理が中断された。このとき、システムが「中途半端な状態」を許容してしまえば、そのデータベースはもはや信頼に値しない。
多くの開発者は `On Error GoTo` を書けば安心だと勘違いしている。しかし、トランザクション処理において、単なるエラーハンドリングは不十分だ。我々が実装すべきは、「不完全な状態を徹底的に拒絶する、堅牢なアーキテクチャ」である。
本稿では、Access VBAにおけるDAO(Data Access Objects)を用いたトランザクション処理の極意を伝授する。
1. なぜ、あなたのトランザクションは「不完全」なのか
まず、アーキテクチャの根幹を理解せよ。DAOにおいてトランザクションを制御するのは `Database` オブジェクトではなく、その上位概念である `Workspace` オブジェクトである。
よくある失敗パターンは、エラーが発生した際に `Rollback` を呼び忘れる、あるいはエラーハンドリングの中でさらに別のエラーが発生し、`Rollback` 自体がスキップされるケースだ。
致命的なアンチパターン
' ❌ 避けるべき設計:不完全なエラーハンドリング
Sub BadTransaction()
On Error GoTo Err_Handler
Dim db As DAO.Database
Set db = CurrentDb
DBEngine.Workspaces(0).BeginTrans ' トランザクション開始
' 何らかの更新処理...
' ここでエラーが発生すると...
DBEngine.Workspaces(0).CommitTrans ' ← ここに到達しない!
Exit Sub
Err_Handler:
MsgBox "エラーが発生しました"
' ❌ Rollbackがないため、トランザクションが「開いたまま」になり、
' 他のユーザーがテーブルをロックしたり、整合性が崩れたりする。
End Sub
このコードでは、エラーが発生した際、トランザクションは「開始されたまま」の状態(Orphaned Transaction)となる。これはデータベースの整合性を破壊するだけでなく、リソースのロックを引き起こし、システム全体のパフォーマンスを著しく低下させる。
2. 守護神としての「Exit Pattern」設計
プロフェッショナルのコードは、「成功ルート」と「エラールート」、そしてそれらを統合する「クリーンアップ(終了)ルート」を明確に分離する。これを私は「Exit Pattern」と呼んでいる。
堅牢な設計の要件は以下の3点だ。
1. 状態フラグの管理: 現在トランザクション中であるかを明示的に保持する。
2. 強制的なロールバック: エラー時は必ず `Rollback` を試みる。
3. リソースの解放: `Set Nothing` によるオブジェクトの破棄を確実に行う。
3. 【プロダクション仕様】極限のトランザクション・テンプレート
以下に、そのまま実務の基幹ロジックに組み込める、最高レベルの堅牢性を備えたテンプレートを示す。このコードは、単に動くだけではない。「何が起きても、データは元の状態に戻す」という強い意志を持った設計である。
''/**
'* プロシージャ名: ExecuteRobustTransaction
'* 概要: データの整合性を担保したトランザクション処理の実行
'* 戻り値: Boolean (成功時 True, 失敗時 False)
'**/
Public Function ExecuteRobustTransaction() As Boolean
' 1. オブジェクト宣言
Dim ws As DAO.Workspace
Dim db As DAO.Database
Dim rs As DAO.Recordset
' 2. 状態管理用フラグ
Dim bInTransaction As Boolean: bInTransaction = False
Dim bSuccess As Boolean: bSuccess = False
' エラーハンドリングの開始
On Error GoTo Err_Handler
' 3. オブジェクトの初期化
Set ws = DBEngine.Workspaces(0)
Set db = CurrentDb
' 4. トランザクションの開始
ws.BeginTrans
bInTransaction = True
Debug.Print "Transaction Started."
' ==========================================================================
' 【メインロジック領域】ここから業務処理を記述する
' ==========================================================================
' 例: 在庫の更新
Set rs = db.OpenRecordset("T_Inventory", dbOpenDynaset)
rs.Edit
rs!StockQty = rs!StockQty - 10
If rs!StockQty < 0 Then Err.Raise vbObjectError + 513, , "在庫が不足しています。"
rs.Update
rs.Close
Set rs = Nothing ' 途中でエラーが起きても、Exit_Handlerで処理されるよう配慮
' 例: 受注履歴の追加
db.Execute "INSERT INTO T_Orders (OrderDate, Qty) VALUES (Now(), 10)", dbFailOnError
' ==========================================================================
' 【メインロジック領域】ここまで
' ==========================================================================
' 5. 全ての処理が成功した場合のみコミット
ws.CommitTrans
bInTransaction = False ' コミットに成功したのでフラグを倒す
bSuccess = True
Debug.Print "Transaction Committed Successfully."
Exit_Handler:
''/**
'* クリーンアップ領域: 成功・失敗に関わらず必ず通過する
'**/
On Error Resume Next ' クリーンアップ中のエラーで止まらないようにする
' トランザクションが未完了(エラー発生時)ならロールバックを実行
If bInTransaction Then
ws.Rollback
Debug.Print "Transaction Rolled Back."
End If
' オブジェクトの解放(メモリリーク防止)
If Not rs Is Nothing Then rs.Close: Set rs = Nothing
Set db = Nothing
Set ws = Nothing
' 結果を返す
ExecuteRobustTransaction = bSuccess
Exit Function
Err_Handler:
''/**
'* エラーハンドリング領域
'**/
' エラー内容をログ出力、あるいはユーザーに通知(実務ではログテーブルへの書き込みを推奨)
Debug.Print "Error Number: " & Err.Number
Debug.Print "Error Description: " & Err.Description
' ユーザーへの通知(必要に応じて)
MsgBox "致命的なエラーが発生しました。処理を中断し、データを保護しました。" & vbCrLf & _
"Error: " & Err.Description, vbCritical, "システムエラー"
' クリーンアップ領域へジャンプ
Resume Exit_Handler
End Function
4. チーフアーキテクトの視点:設計における「重み」
このテンプレートを使いこなすために、以下の3つの設計思想を脳に刻んでおけ。
① `bInTransaction` フラグの重要性
なぜ `If bInTransaction Then` が必要なのか? それは、`Rollback` 自体がエラーを起こす可能性があるからだ。例えば、既に接続が切断されている場合、`Rollback` を呼ぶと新たなエラーが発生し、本来の「エラー報告」が隠蔽されてしまう。フラグ管理を行うことで、「今、本当にロールバックが必要な状態なのか」を論理的に判断できる。
② `On Error Resume Next` の使い所
`Exit_Handler` 内での `On Error Resume Next` は、決して「手抜き」ではない。これは「後片付けを完遂させるための防衛策」だ。クリーンアップ中にエラーが発生してプロシージャが異常終了すると、オブジェクトの解放が不完全になり、メモリリークやデータベースのロックが残るリスクがある。
③ 冪等性(べきとうせい)への意識
トランザクションを組む際、その処理が「何度実行されても同じ結果になるか」を常に考えろ。トランザクションは「失敗したらゼロに戻す」ためのものだが、設計者が「失敗した後に、手動で中途半端なデータを直す」ような運用を前提にしてはいけない。コードは、「失敗した瞬間に、あたかも何もなかったかのように振る舞う」べきなのだ。
まとめ
Access VBAによる業務ツール開発において、DAOのトランザクション管理は「あれば良いもの」ではなく、「絶対に必要な防波堤」である。
今回紹介した `Exit Pattern` をテンプレートとして採用し、エラーが発生した際に「データの整合性が守られていること」をシステム的に保証せよ。それが、使い手(ユーザー)からの信頼を勝ち取る、真のプロフェッショナルエンジニアへの第一歩だ。
