Access,VB5,Jetデータベースエンジンを利用したC/Sシステム構築に関する考察,実験および実践
いきなりデータベースサーバー完成!
秋月 巌 AKIZUKI,Iwao
| 新しいデータベースエンジン |
|---|
朗報がある.Jetデータベースエンジンの後継にあたるデータベースエンジンと,Microsoft SQL Server 7.0のデータベースエンジンが共通化される.これによりJetデータベースエンジンがSQL Serverよりも遅いという既存のイメージは少なくなるだろう.データベースエンジンの共通化については,先月号の原稿を書いた時点ですでに確認されていたのだが,私はまだ口止めをされていた.他誌に速報が掲載されたので,すでに解禁なのだと判断していいはずである.
これがMicrosoft SQL Serverにとって朗報であるのかどうかはわからないが,少なくともJetデータベースエンジンにとっては無条件に喜べる.名称もおそらくJetデータベースエンジンではなくなるに違いない.
Microsoft SQL Serverとエンジンが共通化されることで期待できるのは,データベースエンジンそのものがマルチスレッドに対応する可能性があることである.現行のJetデータベースエンジンもマルチスレッドに対応しているが,それはマルチユーザーに対してマルチスレッドに対応する,という意味ではない.あくまで,キャッシュの制御をマルチスレッドで実施するということであり,あくまで,データベースエンジンは1つのプロセスを独占する.
もちろん,期待は期待のまま裏切られる可能性が高い.何故なら,各ユーザーへの接続がマルチスレッド化できるエンジンだからといって,ファイル共有型の新しいデータベースエンジンがシングルスレッドで動作するとは限らないからである.
いずれにしてもこの連載で企画している内容は,ODBCドライバーを提供するデータベースエンジンならば汎用で使えるので問題はない.だから,新しいデータベースエンジンが新機能を提供してくれば,その機能を使用することができるのである.
| メリットは価格とメインテナンス性,そしてインターネット対応 |
|---|
先月号で,これらファイル共有型のデータベースエンジンを利用してデータベースサーバーを作成することを約束した.メリットはコストとメインテナンスの容易さである.しかし,Microsoft SQL Server 7.0の速報を読んだ方ならばご存知だと思うが,この新しいデータベースサーバーは高いレベルのメインテナンス性を実現している.だから,この連載で作成するデータベースサーバーのメインテナンス性のメリットは,いずれ半減することになるだろう.しかし,それでも強力なコストメリットは残るし,他にも強力な利点があるのだ.
それは,このデータベースサーバーがクライアントとサーバーの通信にはODBCを利用しないため,インターネット上での配置が自由だということである.つまりセキュリティの問題を気にしなければ,自宅のクライアントPCから,インターネットプロバイダを経由して会社のデータベースサーバーにアクセスすることができるようになる.Webを使ったデータベースシステムと同様の使い方ができるのだ.
| ActiveX Documentsの優位性 |
|---|
この利用法はActiveX Documentsを使ったデータベースシステムを構築するのに最適である.最近ではみんながトーンダウンしているが,Webアプリケーションの低TCOとクライアント/サーバー(以下C/S)システムの柔軟性を両立させるActiveX Documentsは,現在のコンピュータ環境を巡る問題をもっとも高い次元で解決する優れた方法である.ただ今まではODBCドライバのクライアントへのインストールの問題だとか(これは解決可能である),インターネットをパスできないとかいった制限のために不当に冷遇されてきたのである.
| Remote Data Serviceについて |
|---|
ここで最新の技術情報にくわしい読者ならば,あるテクノロジーが頭に思い浮かぶはずだ.MicrosoftのRDS(Remote Data Service)である.私の記事を含め本誌でも何度か取り上げられている.このメカニズムはインターネットパスするデータアクセスメカニズムだが,日本語版の正式なリリースはされていない.Ver1.0をテストした段階では,英語版で日本語を扱うには独自に開発したパッチをあてなければならず,速度も遅かった.コンセプトは優れているのだが妙に複雑化してしまい,今の時点では使えないテクノロジーに私は分類している.
図1:データベースサーバー
図2:データベースクライアント |
| いきなりデータベースサーバー |
|---|
とにかく,無償で提供されるデータベースエンジンを使用して,Microsoft AccessやVisual Basicからアクセスでき,インターネット経由も可能なデータベースサーバーを順序だてて開発しようというのが本稿の目的だ.それでいきなり完成したデータベースサーバーが図1,サーバーにアクセスするためのクライアントが図2である.クライアントの「SQL文」項目にSQL文入力して「実行」ボタンをクリックすると,サーバーがデータベースを検索して値をクライアントに返して表示する.開発にかかった時間は約2時間だった.1時間を目標に開発していたが,可変のレコード数や項目に対応しようと欲張っているうちに時間が超過してしまったのである.
| 使用法 |
|---|
使い方は簡単だ.
まず準備段階として,何かサンプルのデータをODBCに登録しておく必要がある.登録するには,コントロールパネルからODBCを開く.「システムDSN」タブを表示して「追加」ボタンをクリックしてから(図3),「Microsoft Access Driver」を選択する.「完了」にすると(図4),Accessドライバの設定画面が表示されるので,「データソース名」項目に任意の文字列を入力してから「選択」ボタンをクリック(図5),使用するmdbファイルを選択し「OK」(図6),これで登録完了だ(図7).
図3:データソースに追加する![]() |
図4:Microsoft Access Driverを選択
|
図5:任意のデータソース名をつける![]() |
図6:元になるmdbファイルを選択
|
図7:データソースに追加された
|
準備が終わったところでサーバーアプリケーションを起動し,今登録した任意のデータソース名を入力して「データベース接続」ボタンをクリックする.これで,サーバーが起動したことになる.
次にクライアントアプリケーションを起動してSQLを実行する.SQL文は,例えば次のように記述すればいい.
SELECT * FROM [テーブル名] WHERE [項目名] = [検索条件]
そして「実行」ボタンをクリックすると,結果がその下に表示される(図1・2参照).もちろん,テーブル名に指定するテーブルはそのmdbファイルに存在している必要があるし,項目名の項目はテーブルに含まれている必要がある.そして項目名と検索条件の型が一致している必要がある.また当然のことだが,先にサーバーが起動していないと接続できないので注意してほしい.
1台のマシンで試すことを考えて,デフォルトでサーバーへの接続時に自分のマシンを探しにゆくようになっている.ネットワークで接続した状態で試したい場合には,クライアントのフォームのLoadイベントプロシージャにサーバーのマシン名を指定する.
現段階では,このアプリケーションはエラートラップをおこなっていないので,間違ったSQL文を指定するとプログラムがシャットダウンしてしまう.そうするとサーバーを再起動し,同じくクライアントも再起動しなければならなくなるので,この点も注意してほしい.また,データ転送の最適化がなされていないため,結果セットが大きなクエリーを実行すると表示されるのにだいぶ待つことになる.このあたりが今後の課題になるだろう.
| ActiveX Documentsでの使用に最適 |
|---|
現在はまだデータの検索しかサポートしていないが,データの参照だけならばこれでも実用になるはずである.特にActiveX Documentsを使ってWebアプリケーションを作成すれば,インターネット経由でデータベースの参照ができるシステムがHTMLを使うことなく実現できるようになる.
ポイントになるのは,指定したODBCデータソースのデータベースがAccessのようなファイル共有型のデータベースであってもデータベースサーバーとして動作していることである.だから,かなり大量のデータから検索を実行しても,ネットワークのトラフィックによってパフォーマンスが極端に劣化することはない.サーバーでODBCのデータソース名を設定しているが,これはサーバーアプリケーションとデータベースが接続するために指定しているものであって,クライアントとサーバーが通信するためのものではない.
| TCP/IP通信を行なうコントロール |
|---|
こんなに短時間でデータベースサーバーが完成したのは,通信のメカニズムにナルボ社製の市販コントロール「Communication Collection Ver.1J」に含まれるEasy Socketコントロールを利用したからである.同様の機能をもつ製品には,Visual Basicに付属の「Winsockコントロール」,ウィル社の「TCPIPOCX」などがある.また,Desaware社の「SpyWorks Pro」(日本語版は文化オリエント社)の最新版にもSocketをコールする機能が追加された.ナルボのEasy Socketコントロールを選択したのはもっとも抽象度が高いことと,マルチセッションに対応していること,加えて価格が妥当(再配布ライセンスつき\30,000)であるからだ.好みに応じて他社製品を使うのもよいし,もちろん,Winsock APIを自分でコールするのもよい.
| ナルボ社のEasy Socketコントロールを利用 |
|---|
本稿で説明しているサンプルプログラムを使用するには,Easy Socketコントロールが必要である.そのため,サンプルを使用する前にインストールしなくてはならない.コントロールを使用するためのインストーラを作成しておいた.このインストーラを使ってインストールされるコントロールには,デザイン時のライセンスは含まれない.再配布ライセンスを使って作成しているためである.しかしこのインストーラによってインストールされたコントロールを使って,読者はサンプルプログラムを開くことができるし,サンプルを改変することもできる.また,開発環境から実行して動作を確認することもできる.ただ,その環境でコンパイルされたモジュールを実行することはできない.しかし,学習と調査のためには,十分だろうと思う.
図8:テキストボックスに文字列を入力して送信
図9:送信した内容が表示される |
| 簡単なサンプルアプリケーション |
|---|
この連載で使うような目的でEasy Socketコントロールを使う分には,TCP/IPの知識はまったく必要ないといってよい.接続時にポート番号を指定しなければならないことを知っておくことくらいだろうか.
図7,8は,Easy Socketの使用法を説明するための簡単なサンプルアプリケーションである.図7のクライアントで送信した内容が図8のサーバーに受信されてデータが表示される.
リスト1,2,それに表1がこのプログラムのすべてである.それでは,順を追ってこのサンプルアプリケーションを説明してゆこう.
このアプリケーションも,先にサーバー起動しておく必要がある.まず,受信サーバーのフォームのLoadイベントプロシージャに記述された次の1行で,サーバーは通信待ちモードに移行する.
KnSocket1.Open
この状態で,LocalPortプロパティはデフォルトの10000に設定されている.
次におきるアクションは,受信サーバーのフォームのLoadイベントである.イベントプロシージャに記述された次の1行はRremoteHostNameプロパティに自分のマシンを指定している.
KnSocket1.RemoteHostName = KnSocket1.LocalHostName
これは,サンプルを1台のコンピュータで稼動させるための配慮である.ネットワーク経由で使う場合には,右辺にマシン名を指定する.そして,次の1行で接続を開始する.
KnSocket1.Open
RemotePortプロパティがサーバーのLocarPortプロパティ10000に設定されていることも覚えておくべきだ.これによりサーバーとクライアントの接続が確立され,Easy Socketコントロールがグリーンに変化する.接続に成功した証拠である.
送信ボタンのClickイベントプロシージャに記述された処理は,データ送信処理である.まず,2つのテキストボックスに入力された内容をバリアント型の配列に格納する.Easy Socketはバイナリ型もストリング型も扱えるのだが,バリアント型の配列が使えるのは興味深い機能である.これでプロトコル設計が非常に楽になる.
実際の送信処理を行なっているのが次の1行である.SendDataメソッドの引数にバリアント配列を指定している.
KnSocket1.SendData SendArray
一方,受信するサーバーはReceiveDataイベントプロシージャによって,テキストボックスに送信されたデータを表示している.
Private Sub KnSocket1_ReceiveData(ByVal nIndex As Integer, ByVal vData As Variant)
Text1.Text = Text1.Text + vData(0) & vbCrLf & vData(1)
End Sub
ReceiveDataイベントは,バリアント型のデータを受信した場合に発生するイベントであるため,受信したデータバリアント型のデータを引数として受け取ることができる.受け取るデータの型に合わせて,複数のイベントが用意されているのもEasy Socketコントロールの特徴である.
このイベントプロシージャでは,引数で受け取ったデータをテキストボックスに表示している.
リスト1:送信クライアントに記述された全コード Private Sub Form_Load() ' 2台のマシンで稼動させる場合には, ' 次のプロパティ設定でサーバーマシン名を設定する KnSocket1.RemoteHostName = KnSocket1.LocalHostName KnSocket1.Open End Sub Private Sub Command1_Click() Dim SendArray(2) SendArray(0) = Text1.Text SendArray(1) = Text2.Text KnSocket1.SendData SendArray End Sub |
リスト2:受信サーバーに記述された全コード Private Sub Form_Load() KnSocket1.Open End Sub Private Sub KnSocket1_ReceiveData(ByVal nIndex As Integer, ByVal vData As Variant) Text1.Text = Text1.Text + vData(0) & vbCrLf & vData(1) End Sub |
表1:送信クライアントで変更したEasy Socketのプロパティ
| プロパティ | 値 |
|---|---|
| LocalPort | 10001 |
| RemotePort | 10000 |
| SocketPosition | 1-KnSocketCLIENT |
| 応用 |
|---|
このサンプルを見れば,インターネットでも社内でも利用できる掲示板やチャットが簡単にできることが想像できるだろう.この機能だけだとサーバーに表示されるだけだが,これをクライアントに送り返すだけでいい.
このようにもテキストをやりとりしてデータを表示するだけではただのコミニュケーションウェアだが,通信で送られたデータをきっかけにプログラムが起動すれば,これは立派な分散処理だ.実はサーバーと名のつくプログラムはすべて分散処理アプリケーションだということができる.分散された処理をサービスするのがサーバーだからである.
| 分散処理 |
|---|
Windows NT 4.0が登場したとき,すごいOSが登場するものだと驚かされた.仕様からドロップするかと思われたDCOM(Distributed Component Object Model…再利用可能コンポーネントを使った,分散システムを実現する技術)がWindows NT 4.0でサポートされたからだ.まだ実用的ではないにしても,今後のシステムのスタイルを決定する重要な進化だと思ったのである.
| PCDNのセミナーで得たもの |
|---|
この考えに疑問を持ち始めたのは,PCDNの最初のセミナーの後である.私は受講者として参加するはずだったのだが,都合で行けなくなったので代わりに妻が出席した.彼女が持ってかえってきたCD-ROMに収録されている福岡寿和氏のTCP/IPアプリケーションのサンプル(このサンプルは本誌97年7月号に収録されている)を家で実行しながら,彼女は次のように言ったのである.
「この後に酒井さん(酒井法雄氏)が話をしてDCOMの解説をしながら,全て福岡さんのサンプルアプリケーションの方法でDCOMの代わりにができちゃいますね,と言っていたわ」
その話を聞いたとき,Visual Basicに付属のコントロールのできてしまうような方法で,DCOMの代わりができるわけがないと,正直言って半信半疑だった.DCOMには圧倒的な優位性があるはずであると….
| DCOMの優位性? |
|---|
しかし,そんなものはないのである.TCP/IPのサーバーアプリケーションで何でもできてしまうのだ.ではDCOMの優位性とは一体何なのか考えてみよう.
まずひとつは,Windowsがサポートするどのようなプロトコルでも動作することがあげられる.私はTCP/IPが嫌いだ.NetBEUIと違って,IPアドレスだとか,DHCPだとか,いろんなものを設定しなければならない.それに比べてNetBEUIは配線をつなぐだけで接続できることもある.しかしイントラネットが次世代のシステムの標準となる今日においては,TCP/IPネットワークは必須の装備だということができる.ならばTCP/IPで動作するサーバーアプリケーションに対してDCOMが圧倒的に有利だとはいえない.
| OSによるサポートが強み |
|---|
つぎに,サーバーシステムを常時起動しておく必要がないことはDCOMのメリットだ.TCP/IPサーバーアプリケーションだと,常にサービスを行なうアプリケーションを起動しておく必要がある.これはDCOMがOSレベルでサポートされていることからくる強みだ.ただ実際には単にOSがラウンチャーの代わりを果たしているだけだということもできる.アプリケーョンを起動するためにTCP/IPサーバーを作成して,それをひとつ常駐させておくだけで同様の効果を得ることができる.また,現在のメモリ価格やアプリケーションの起動コストを考えれば,サーバーアプリケーションが常駐することのデメリットは少なく,DCOMのメリットもそれほど大きいものではない.
DCOMのDCOMたる由縁は,リモートにあるオブジェクトをポインタで扱うことができることだ.DCOMのすごい点は,まさしくこの一点に集約できる.しかしながら,リモートにあるオブジェクトのポインタなどは操作したくないというまともなエンジニアならば,この点にもメリットは感じないだろう.たとえDCOMであってもオブジェクトの連携は専用のプロトコルを作成して行なおうと考えるはずである.
| DCOMの代わりに |
|---|
つまりこのように考えていくと,DCOMというものは,コンセプトはすばらしいが,実は既存の安心できるテクロジーで問題なく代替できることがわかる.またマシン名とポート番号を指定するだけなので,DCOMのような神経質な設定をする必要もない.Windows 95もDCOMをサポートしたが,それを使おうと思う人はかなり勇気のある人だろう.
ただ,COMテクノロジーそのものは,レジストリデータベースに依存するというアーキテクチャ上の問題はあるにしても,十分に高い信頼性で動作する.そもそもCOMに依存せずにVisual Basicでプログラミングすることなどできないし,今日においてはWindows自体がCOMに依存しているのだから,これは避け難い.
| データベースサーバーは分散処理アーキテクチャ |
|---|
さて,話をデータベースサーバーにもどそう.DCOMなど,新しいテクノロジーとして扱われるようになったのでつい忘れがちなのが現状だが,C/Sシステムは代表的な分散処理アーキテクチャである.
図1のデータベースサーバーは,クライアント側で入力したSQL文をEasy Socketコントロールを使用してサーバーに送り,結果のレコードセットをクライアントにバリアント配列として返している.ソースコードの全部を記事末に掲載するが(リスト3,4),かなり単純な構造であることがわかるだろう.コードの詳細については,次号で解説する.
このデータベースサーバーを実行すると,処理に要した時間がフォームに表示される.クライアント側に表示される時間は,SQLの送信から結果セットの受信まで処理のすべてにかかった時間であり,サーバーに表示される時間はデータベースへのアクセスにかかった時間である.
| バリアント配列の操作速度が問題 |
|---|
開発している途中のプロセスではかなり高速だったのだが,汎用的に使用できるように改造したためレコード数と項目数の積に比例してバリアント配列の要素数が増加した.そのため結果セットのサイズがある程度以上になると,極端にパフォーマンスが劣化する.Visual Basicのパリアント配列の処理速度に問題があるのだろう.使いやすくて便利なのだが,パフォーマンスの向上のためバリアント配列の使用はあきらめなければならないかもしれない.来月までにはもう少し進歩するはずである.今月は確定申告の準備で妻が忙しかったため私が自分でコーディングしたが,来月は彼女がコードを実装してくれるはずなので,もう少しまともになるはずだ.
| マルチユーザーにも対応,しかし… |
|---|
なお,このデータベースサーバーは,マルチユーザーでも正常に動作する.ただし実行に使用されるスレッドはひとつである.つまりあるユーザーが時間のかかるクエリーを実行していると,次のユーザーはその処理が終わるまで待たされることになる.こんなことは,まともなデータベースサーバーでは許されない.この問題に対しては秘策を用意してあるので,今後に期待していただきたい(もちろんファイル共有型のデータベースをシングルプロセスのマルチスレッドで使用するような危ないことはしない.安全で効果的な方法がある).
リスト3:SQLデータベースサーバーの全ソースコード
Option Explicit
Private Declare Function timeGetTime Lib "WINMM" () As Long
Private mlngStartTime As Long
Private mlngEndTime As Long
Private en As rdoEnvironment
Private cn As rdoConnection
Private rs As rdoResultset
Private daodb As Database
Private Sub cmdA_Click()
mlngStartTime = timeGetTime()
Set rs = cn.OpenResultset(Text2.Text, rdOpenKeyset, _
rdConcurReadOnly, rdExecDirect)
mlngEndTime = timeGetTime()
lbl3 = (mlngEndTime - mlngStartTime) / 1000
End Sub
Private Sub Command1_Click()
Set en = rdoEnvironments(0)
Set cn = en.OpenConnection(dsName:=Text1, _
Prompt:=rdDriverCompleteRequired)
End Sub
Private Sub Form_Load()
KnSocket1.Open
End Sub
Private Sub Form_Unload(Cancel As Integer)
On Error GoTo errLabel
cn.Close
Set cn = Nothing
On Error GoTo 0
Exit Sub
errLabel:
Debug.Print "Not Connection"
End Sub
Private Sub KnSocket1_ReceiveData(ByVal nIndex As Integer, ByVal vData As Variant)
Text2.Text = vData
cmdA_Click
Dim oneTime
Dim i As Integer
Dim recCount As Long
Dim arr() As Variant
Dim headerCount As Integer
Dim fieldCount As Integer
Dim MaxFieldCount As Integer
MaxFieldCount = rs.rdoColumns.Count
oneTime = 10
headerCount = 2
recCount = headerCount
Do While Not rs.EOF
ReDim Preserve arr((recCount + oneTime + 1) * MaxFieldCount)
Debug.Print recCount + oneTime
For i = 1 To oneTime
For fieldCount = 0 To MaxFieldCount - 1
arr(recCount) = rs(fieldCount)
recCount = recCount + 1
Next
rs.MoveNext
If rs.EOF Then
Exit For
End If
Next
Loop
arr(0) = recCount - headerCount
arr(1) = MaxFieldCount
KnSocket1.Sockets(nIndex).SendData arr
rs.Close
Set rs = Nothing
End Sub
|
リスト4:データベースクライアントの全ソースコード
Private Declare Function timeGetTime Lib "WINMM" () As Long
Private mlngStartTime As Long
Private Sub Command2_Click()
KnSocket1.SendData Text1.Text
mlngStartTime = timeGetTime()
Text2.Text = ""
End Sub
Private Sub Form_Load()
' 2台のマシンで稼動させる場合には,
' 次のプロパティ設定でサーバーマシン名を設定する
KnSocket1.RemoteHostName = KnSocket1.LocalHostName
KnSocket1.Open
End Sub
Private Sub KnSocket1_ReceiveData(ByVal nIndex As Integer, ByVal vData As Variant)
Dim reCount As Long
Dim i As Long
Dim headerCount As Integer
Dim maxFieldCount As Integer
Dim fieldCount As Integer
Dim arraySize As Long
Dim arrayPosition As Long
arraySize = vData(0)
maxFieldCount = vData(1)
recCount = arraySize / maxFieldCount
Text2.Text = vData(0) & "レコード" & maxFieldCount & "項目" + vbCrLf + vbCrLf
headerCount = 2
i = 0
arrayPosition = headerCount
Do While i < recCount
fieldCount = 0
Do While fieldCount < maxFieldCount
Text2.Text = Text2.Text & vData(arrayPosition) & " "
fieldCount = fieldCount + 1
arrayPosition = arrayPosition + 1
Loop
Text2.Text = Text2.Text + vbCrLf
i = i + 1
Loop
mlngEndTime = timeGetTime()
Label3 = (mlngEndTime - mlngStartTime) / 1000
End Sub
|