【実務・中級編】DAOのトランザクション処理中に発生する「予期せぬ中断」への備え – Access VBA解析バイブル

スポンサーリンク
スポンサーリンク

データの整合性を守る「防波堤」を築け: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` をテンプレートとして採用し、エラーが発生した際に「データの整合性が守られていること」をシステム的に保証せよ。それが、使い手(ユーザー)からの信頼を勝ち取る、真のプロフェッショナルエンジニアへの第一歩だ。

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