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

スポンサーリンク

こんにちは!Access VBAでのシステム開発、順調に進んでいますか?
「マクロの記録」から一歩踏み出し、自らコードを書いて業務を自動化していく過程は、本当にエキサイティングですよね。

さて、今回は実務で避けて通れない「トランザクション(処理の確定と巻き戻し)」、特に「トランザクションのネスト(入れ子構造)」について、少し踏み込んだ話をしたいと思います。

「データを登録する処理の中で、別の登録処理も呼び出したい。それぞれ独立してロールバック(やり直し)できるようにしたいな……あれ、トランザクションの中にさらにトランザクションって作れるんだっけ?」

そんな疑問を持ったことはありませんか?
ここをクリアできれば、あなたのAccess VBAスキルは間違いなく一段階上のステージに到達します。優しく、そして本質的なところまでしっかりと解説していきますね。ここをクリアすれば、複雑な業務ロジックも怖くありませんよ!

—

1. トランザクションの基本と「本当のところ」

まずはおさらいです。トランザクションとは、「一連のデータベース更新処理を『すべて成功』か『すべて失敗(なかったこと)』のどちらかにする仕組み」のことです。途中でエラーが起きても、データベースが中途半端な状態になるのを防いでくれます。

Accessの標準エンジンであるDAO(Data Access Objects)では、次のようにトランザクションを制御します。

‘ DAOのトランザクションの基本
DBEngine.BeginTrans ‘ トランザクション開始
On Error GoTo ErrorHandler

‘ ここにデータ追加や更新処理を書く
‘ …

DBEngine.CommitTrans ‘ すべて成功したら確定
Exit Sub

ErrorHandler:
DBEngine.Rollback ‘ エラーが起きたらすべて巻き戻す
MsgBox “エラーが発生したため、処理を中止しました。”, vbCritical
End Sub

ここで、今日の本題です。
「この `BeginTrans` の中に、さらに別の `BeginTrans` を入れて、入れ子(ネスト)にすることはできるのか?」

答えを言いましょう。
DAOのトランザクションは、仕様上「ネスト(入れ子)が可能」です。最大5つまで多重にトランザクションを重ねることができます。

「おっ、じゃあ安心じゃん!」と思ったそこのあなた。ちょっと待ってください。ここからがプロのエンジニアとしての腕の見せ所です。「技術的にできること」と「実務で安全に使えること」は別問題なのです。

—

2. なぜDAOの本当のネストは危険なのか?

DAOでは `BeginTrans` を呼ぶたびにトランザクションの階層が深くなります。しかし、親と子のトランザクションの間には、知っておかなければならない致命的な罠があります。

それは、「Rollback(ロールバック)の挙動」です。

  • 子トランザクションで `Rollback` を実行すると、その親を含めたすべてのトランザクションがまとめて巻き戻される場合があります(※AccessのJet/ACEエンジンの仕様や実装方法に依存します)。
  • 「子だけの処理をなかったことにして、親の処理は続けたい」というシチュエーションでは、DAOのネイティブなネストは期待通りに動かないことが多いのです。

また、ADO(ActiveX Data Objects)を使う場合、接続するデータベース(SQL Serverなど)によっては「ネストされたトランザクションはサポートしていません」という非情なエラーが返ってくることもあります。

そのため、現場のシニアエンジニアたちは、DAOやADOのネイティブなネスト機能に頼らず、「フラグ管理による擬似的なネスト(セーブポイント方式)」というテクニックを好んで使います。

—

3. 実践!フラグ管理による「擬似ネスト」の極意

ここからは、実務で確実にあらゆる複雑なロジックを制御できる「擬似的なトランザクション・ネスト」の書き方をマスターしましょう。

考え方はシンプルです。
「すでに外側でトランザクションが始まっているかどうかをフラグ(変数)で監視し、始まっていなければ自分がトランザクションの親になり、始まっていれば単なる処理の一部として実行する」というアプローチです。

以下のサンプルコードを見てください。メインの処理(親)から、共通のデータ登録処理(子)を呼び出すシナリオです。

‘ ====================================================================
‘ 親処理:メインの業務ロジック
‘ ====================================================================
Sub ExecuteMainBusinessLogic()
Dim db As DAO.Database
Set db = CurrentDb

‘ トランザクションがすでに開始されているかを判定するフラグ
‘ (Access標準のトランザクション中はエラーが発生するため自前で管理)
Dim isTransStarted As Boolean
isTransStarted = False

On Error GoTo ErrorHandler

‘ 【親のトランザクション開始】
‘ ※DAOではDBEngine.BeginTransを使います
DBEngine.BeginTrans
isTransStarted = True
Debug.Print “【親】トランザクション開始”

‘ 1. メイン側のデータ更新処理
db.Execute “UPDATE T_Orders SET Status = ‘処理中’ WHERE OrderID = 101”, dbFailOnError

‘ 2. 共通の子処理を呼び出す(ここで子側にトランザクションを渡すイメージ)
‘ ※ここでは同じDBオブジェクトを渡しています
Call ExecuteSubProcess(db, isTransStarted)

‘ すべて成功したので確定
DBEngine.CommitTrans
Debug.Print “【親】トランザクション確定(Commit)”

MsgBox “すべての処理が正常に完了しました!”, vbInformation
Exit Sub

ErrorHandler:
‘ エラーが発生した場合、自分がトランザクションを開いていればロールバック
If isTransStarted Then
DBEngine.Rollback
Debug.Print “【親】エラー発生のためロールバック実施”
End If

MsgBox “エラー番号: ” & Err.Number & vbCrLf & Err.Description, vbCritical
End Sub

‘ ====================================================================
‘ 子処理:単体でも、親から呼び出されても動く共通ロジック
‘ ====================================================================
Sub ExecuteSubProcess(db As DAO.Database, Optional ByVal isParentTrans As Boolean = False)
‘ 子単体で呼び出された場合を考慮したローカルフラグ
Dim needsTrans As Boolean
needsTrans = False

‘ もし親からトランザクションが引き継がれていなければ、自分が親になる
If Not isParentTrans Then
DBEngine.BeginTrans
needsTrans = True
Debug.Print ” 【子】単独実行のため、ここでトランザクション開始”
Else
Debug.Print ” 【子】親のトランザクションに相乗りします”
End If

‘ 子側のデータ更新処理
db.Execute “INSERT INTO T_History (LogDate, Action) VALUES (Now(), ‘受注処理実行’)”, dbFailOnError

‘ 子が独自にトランザクションを開始していた場合のみ、ここでコミット
If needsTrans Then
DBEngine.CommitTrans
Debug.Print ” 【子】単独トランザクション確定”
End If

Exit Sub

SubError:
‘ 子が独自にトランザクションを開始していた場合のみロールバック
If needsTrans Then
DBEngine.Rollback
Debug.Print ” 【子】エラーのためロールバック”
End If
‘ エラーを親に伝播(バブルアップ)させる
Err.Raise Err.Number, “ExecuteSubProcess”, Err.Description
End Sub

このコードの美しいポイント

1. 二重管理を防ぐ設計
`isParentTrans` という引数(フラグ)を使って、「今、自分が上位のトランザクションの中にいるかどうか」を完璧に把握しています。これにより、同じデータベース接続に対して二重に `BeginTrans` を発行してエラーになるのを防いでいます。
2. 単体テストが容易になる
`ExecuteSubProcess` は、親から呼ばれたときは親のトランザクションに相乗りし、このプロシージャ単体でテスト実行したときは自分でトランザクションを張って完結します。これが「再利用性の高いコード」の正体です。
3. エラーの適切な伝播
子の中でエラーが起きた場合、単独なら即ロールバック、親にぶら下がっている場合は親の `ErrorHandler` にエラーを投げ(`Err.Raise`)、親側で一網打尽にロールバックさせることができます。

—

まとめ:ここをクリアすればAccess VBAの基本はバッチリ!

今回は、トランザクションのネストという少し高度なテーマについて、DAOの特性と現場で使える「フラグ管理による擬似ネスト」のテクニックを解説しました。

  • DAOのネイティブなネストは挙動が複雑になりがちなので注意する。
  • 実務ではフラグ(引数)を使った「相乗り・自立切り替え方式」が最も安全で確実。
  • エラーハンドリングは「誰がトランザクションを開いたか(責任者)」が責任を持って閉じる(Commit / Rollback)。

この設計思想が頭に入っていれば、Access VBAだけでなく、将来的にC#やVB.NET、あるいはWeb系のデータベースプログラミングに挑戦する時も、必ずあなたの強力な武器になります。

複雑な業務ロジックを綺麗に整理できたときの爽快感は、エンジニアだけの特権です。ぜひ、あなたの開発現場でもこの知見を取り入れて、堅牢で美しいシステムを作ってみてくださいね!

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