Web/DB プログラミング徹底解説

ホーム > Windows 徹底解説 > COM+ トランザクション

COM+ トランザクション

1. はじめに

COM+ にてオブジェクトを Transactional Object として構成した場合、同じトランザクションストリームに入るコンテキストの Happy/Doomed ビットをセットすることにより、そのトランザクション状態の管理を容易にします。

2. 概要

この資料のサンプルコードでは、TestMethod という名前のメソッド内部で、データベースへのレコードの追加を行います。明示的に BeginTrans, CommitTrans または RollbackTrans を呼び出すことにより、トランザクション処理を行うのではないことに注意してください。ここでは、GetObjectContext を用いてオブジェクトコンテキストのインターフェイスポインタを取得し、それを用いて SetAbort や SetComplete を呼び出します。これにより、この処理の成否をコンテキストにマークすることが出来ます。

3. 演習の流れ

  1. コンポーネントの作成
  2. コンポーネントの登録
  3. クライアントの作成
  4. 動作確認

尚、この演習では pubs データベースを利用します。環境をお持ちでない方は、5.補足 を参考にしてデータベースを準備してください。 また pubs という名前のシステム DSN も使用しますのでそれも作成してください。

4. 演習

4.1. コンポーネントの作成

1. コード (trans1.h, trans1.cpp) を準備します。

trans1.h は以下の内容です。

#pragma once

#define _ATL_APARTMENT_THREADED

#include <atlbase.h>
#include <atlcom.h>

[
    object,
    dual,
    helpstring("ITest Interface"),
    pointer_default(unique)
]
__interface ITest : IDispatch {

    [id(1), helpstring("TestMethod")] HRESULT TestMethod ([in] BOOL bAbort);

};

[
    coclass,
    threading("apartment"),
    vi_progid("MyTest.Trans1"),
    progid("MyTest.Trans1.1"),
    version(1.0),
    helpstring("Trans1 class")
]
class ATL_NO_VTABLE CTest : public ITest {

public:
    // Constructor and Destructor
   
CTest () {}

    // Methods
    STDMETHOD(TestMethod)(BOOL bAbort);
};

trans1.cpp は以下の通り。タイプライブラリのインポート文は環境に合わせてパスを変更してください。また接続文字列では sa を使用しています。赤字の部分に sa のパスワードを設定します。

#include "trans1.h"
#include <comsvcs.h>
#import "C:\\Program Files\\Common Files\\System\\ado\\msado26.tlb" no_namespace rename("EOF", "adoEOF")

[
    module(dll, name = "Trans1COM",
    helpstring = "Trans1 Type library")
]
class CTrans1COMModule { };


STDMETHODIMP CTest::TestMethod (BOOL bAbort) {

    _ConnectionPtr pConn = NULL;
    HRESULT hr = S_OK;
    _bstr_t strCmd;

    // Open a connection.
   
pConn.CreateInstance (__uuidof(Connection));
    pConn->Open (
        "DSN=pubs;uid=sa;pwd=password;", (BSTR) NULL, (BSTR) NULL, adConnectUnspecified);

    // Command string.
   
strCmd = "INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Support', 100, 200)";

    IObjectContext* pContext = NULL;
    hr = GetObjectContext (&pContext);

    if (FAILED(hr)) {
        printf ("Failed to get an object context.\n");
        goto closedb;
    }

    try {
        pConn->Execute (strCmd, NULL, adOptionUnspecified);

        if (bAbort) {
            pContext->SetAbort ();
        }
        else {
            pContext->SetComplete ();
        }

        printf ("OK.\n");
    }
    catch(_com_error &e) {

        // Notify the user of errors if any.
       
_bstr_t bstrSource(e.Source());
        _bstr_t bstrDescription(e.Description());

        printf ("Aborted\n");
        printf("Source : %s\n",(LPCSTR)bstrSource);
        printf("Description : %s\n",(LPCSTR)bstrDescription);
    }

closedb:

    if (pConn) {
        if (adStateOpen == pConn->State) {
            pConn->Close ();
        }
    }

    return S_OK;

}

2. 以下の内容を makefile として保存します。

TARGETNAME=trans1
OUTDIR=.\chk
LINK32=link.exe

ALL : "$(OUTDIR)\$(TARGETNAME).dll"

reg:
    regsvr32 $(OUTDIR)\$(TARGETNAME).dll

unreg:
    regsvr32 /u $(OUTDIR)\$(TARGETNAME).dll

test:
    cscript test.vbs

CPPFLAGS=\
    /nologo\
    /EHsc\
    /W3\
    /c\
    /ZI\
    /TP\
    /Fo"$(OUTDIR)\\"\
    /Fd"$(OUTDIR)\\"\
    /DWIN32\
    /D_ATL_ATTRIBUTES\
    /D_ATL_STATIC_REGISTRY\
    /DUNICODE\
    /D_UNICODE

LINK32_FLAGS=\
    kernel32.lib\
    ole32.lib\
    oleaut32.lib\
    uuid.lib\
    comsvcs.lib\
    /OUT:$(OUTDIR)\$(TARGETNAME).dll\
    /DLL\
    /IDLOUT:"$(OUTDIR)\_$(TARGETNAME).idl"\
    /DEBUG\
    /PDB:"$(OUTDIR)\$(TARGETNAME).pdb"\
    /SUBSYSTEM:WINDOWS\
    /IMPLIB:"$(OUTDIR)\$(TARGETNAME).lib"\
    /MACHINE:X86

LINK32_OBJS=$(OUTDIR)\$(TARGETNAME).obj

"$(OUTDIR)\$(TARGETNAME).dll" : "$(OUTDIR)" $(LINK32_OBJS)
    $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS)

"$(OUTDIR)" :
    if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"

.c{$(OUTDIR)}.obj:
    $(CPP) $(CPPFLAGS) $<

.cpp{$(OUTDIR)}.obj:
    $(CPP) $(CPPFLAGS) $<
 

3. ビルドします。

> nmake

4.2 コンポーネントの登録

1. コンポーネントサービス スナップインを開きます。

2. COM+ アプリケーションを作成します。

名前: TransTest
Activation Type: ライブラリアプリケーション

3. TransTest\Components に手順 4.1 で作成した trans1.dll をドラッグアンドドロップします。

4. コンポーネントのプロパティを開き、[Transactions] タブからトランザクションのサポート (Transaction support) を Required に設定します。

4.3 クライアントの作成

1. コンポーネントを呼び出すクライアントは以下のスクリプトです。test1.vbs として保存してください。

Dim obj, bAbort

Set obj = CreateObject("MyTest.Trans1")

bAbort = 1

obj.TestMethod (bAbort)

Set obj = Nothing

bAbort を 1 に設定したときは、trans1.cpp 中の pConn->Execute 呼出し後に SetAbort を呼び出すパスを通ります。一方、bAbort を 0 に設定すると、SetComplete を呼び出すパスを通ります。

4.4 動作確認

1. コンポーネントサービススナップインから、Distributed Transaction Coordinator 以下の Transaction Statistics を開きます。

MEMO: 統計のカウンタをゼロにリセットするには、msdtc を再起動します。

2. 次のコマンドで test1.vbs を実行します。

> cscript test1.vbs

3. Aborted がカウントされていることを確認します。

4. jobs テーブルに何も追加されていないことを確認します。

osql を使用する場合は、以下のようにします。

> osql -E
1> use pubs
2> go
1> select * from jobs order by job_id
2> go
job_id job_desc min_lvl max_lvl
------ -------------------------------------------------- ------- -------
1 New Hire - Job not specified 10 10
2 Chief Executive Officer 200 250
...
13 Sales Representative 25 100
14 Designer 25 100

(14 rows affected)

5. test1.vbs の bAbort を 0 に変更して保存します。

6. もう一度 test1.vbs を実行します。

> cscript test1.vbs

7. 次は Committed カウントが増え、データが追加されたことを確認します。

>osql -E
1> use pubs
2> go
1> select * from jobs order by job_id
2> go
job_id job_desc min_lvl max_lvl
------ -------------------------------------------------- ------- -------
1 New Hire - Job not specified 10 10
...
13 Sales Representative 25 100
14 Designer 25 100
20 Support 100 200

(15 rows affected)

5. 補足: MSDE を用いた pubs データベース のセットアップ

5.1 MSDE のインストール

> setup SAPWD="<password>"

OS を再起動します。

5.2 サンプルデータベース pubs のセットアップ

instpubs.sql は VS.NET のサンプルに付属しています。

>osql -E -iinstpubs.sql

試しに pubs データベースにクエリーを実行します。結果が返れば OK です。

C:\>osql -E
1> use pubs
2> go
1> SELECT * FROM Authors
2> go
au_id au_lname au_fname phone address

state zip contract
----------- ---------------------------------------- -------------------- ------------ ------------
172-32-1176 White Johnson 408 496-7223 10932 Bigge
...(省略)...
UT 84152 1

(23 rows affected)

今回テストに使用する INSERT コマンドをテストします。

1> INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Support', 100, 200)
2> go
(1 row affected)
1> SELECT * FROM jobs ORDER BY job_id
2> go
job_id job_desc min_lvl max_lvl
------ -------------------------------------------------- ------- -------
1 New Hire - Job not specified 10 10
2 Chief Executive Officer 200 250
...
14 Designer 25 100
15 Support 100 200

(15 rows affected)
1> DELETE FROM jobs WHERE job_desc='Support'
2> go
(1 row affected)
1> SELECT * FROM jobs ORDER BY job_id
2> go
job_id job_desc min_lvl max_lvl
------ -------------------------------------------------- ------- -------
1 New Hire - Job not specified 10 10
2 Chief Executive Officer 200 250
...
13 Sales Representative 25 100
14 Designer 25 100

(14 rows affected)
1>

5.3 システム DSN の作成

1. 管理ツールの中から Data Sources (ODBC) を開きます。

2. [System DSN] タブを選択し、[Add] をクリックします。

3. SQL Server を選択して [Finish] をクリックします。

4. ウィザードには以下の図の通りに答えます。

DSN 名は pubs にします。

ここはそのまま [Next] をクリックします。

[Change the default database to] をチェックして pubs を選択します。

ここはデフォルトのまま [Finish] をクリックします。

[Test Data Source] をクリックし、接続テストを行います。

次の画面が表示されれば成功です。