db4oでの排他制御
オブジェクトデータベースのdb4oが面白そうなので、ちまちま弄っています。データベースで一番重要なのは如何にデータを守るかなので、排他制御がとても気になります。と言うわけで簡単なクライアント/サーバーモジュールを作成して実験してみました。が、色々分からないことばかりで、しばらくは勉強&調査が必要そうです。(^^;
基本的にはチュートリアルを参考にしています。
- model.dll データベースに格納するオブジェクト。
- register.exe データベースを初期化する。
- server.exe サーバー。
- client.exe クライアント。ドライバーのポイントをカウントアップする。
以上、4つのアセンブリを作成します。
model.cs
using System; public class Pilot { string _name; int _points; public Pilot (string name, int points) { _name = name; _points = points; } public string Name { get { return _name; } set { _name = value; } } public int Points { get { return _points; } set { _points = value; } } public void AddPoints (int points) { _points += points; } public override string ToString () { return String.Format ("{0}/{1}", _name, _points); } } public class Car { string _model; Pilot _pilot; public Car (string model) { _model = model; } public string Model { get { return _model; } set { _model = value; } } public Pilot Pilot { get { return _pilot; } set { _pilot = value; } } public override string ToString () { return String.Format ("{0}[{1}]", _model, _pilot); } }
チュートリアルまんまですね。
register.cs
using System; using System.IO; using com.db4o; public class App { const string DbName = "mydb"; public static void Main () { if (File.Exists (DbName)) File.Delete (DbName); Db4o.Configure().ObjectClass(typeof(Car)).CascadeOnUpdate (true); Db4o.Configure().ObjectClass(typeof(Car)).CascadeOnDelete (true); ObjectContainer db = null; try { db = Db4o.OpenFile ("mydb"); Car car; car = new Car ("Ferrari"); car.Pilot = new Pilot ("Michael Schumacher", 100); db.Set (car); car = new Car ("BMW"); car.Pilot = new Pilot ("Rubens Barrichello", 99); db.Set (car); } finally { if (db != null) db.Close (); } } }
データベースを作り直してデータを2件追加します。実験しているとデータベースがぐちゃぐちゃになるので、何かと便利。
server.cs
using System; using System.Collections.Generic; using System.IO; using System.Threading; using com.db4o; using com.db4o.messaging; public class Server : MessageRecipient { const string file = "mydb"; const string user = "user"; const string pass = "pass"; const string host = "localhost"; const int port = 1234; private bool stop = false; public void Run () { lock (this) { ObjectServer server = Db4o.OpenServer (file, port); server.GrantAccess (user, pass); server.Ext().Configure().SetMessageRecipient (this); try { if (!stop) { Monitor.Wait (this); } } catch (Exception e) { Console.WriteLine (e.Message); Console.WriteLine (e.StackTrace); } finally { server.Close (); } } } public void ProcessMessage (ObjectContainer conn, object message) { if ("Stop".Equals *1; foreach (Car car in result) Console.WriteLine (car); } }
クライアントからの接続をひたすら待ちます。
client.cs
using System; using System.Collections.Generic; using com.db4o; using com.db4o.messaging; public class App { const string host = "localhost"; const int port = 1234; const string user = "user"; const string pass = "pass"; public static void Main(string[] args) { ObjectContainer client = null; try { Db4o.Configure().ObjectClass(typeof(Car)).CascadeOnUpdate (true); Db4o.Configure().ObjectClass(typeof(Car)).CascadeOnDelete (true); client = Db4o.OpenClient (host, port, user, pass); foreach (Pilot pilot in Query (client)) { pilot.AddPoints (1); client.Set (pilot); } Console.WriteLine ("Before Commit !"); Console.ReadLine (); client.Commit (); Console.WriteLine ("Commit Done!"); Console.ReadLine (); foreach (Pilot pilot in Query (client)) Console.WriteLine (pilot); } finally { if (client != null) client.Close (); Console.WriteLine ("Closed."); } Console.WriteLine ("Exit."); } static IList<Pilot> Query(ObjectContainer client) { IList<Pilot> pilots = client.Query<Pilot>( delegate (Pilot pilot) { /* return pilot.Points > 99 && pilot.Points < 199 || pilot.Name == "Rubens Bariichello";*/ return true; }); return pilots; } }
データベースへの変更を行います。コミット前とコミット後の2回、キー入力待ちを入れています。まず、初期データベースを作成します。
$ register.exe
で、サーバーの起動。
$ server.exe
クライアントの起動。
$ client.exe Before Commit !
ここでキー入力を待ちます。このタイミングでは値を変更しているが、コミットは行っていない状態です。では、もう一つクライアントを起動してみましょう。
$ client.exe Before Commit !
リレーショナルデータベースの感覚だと更新のタイミングでブロックされるのですが、構わず処理が進むようです。このまま両方のclientの処理を進めると後から実行した方が固まってしまいます。これを何度か繰り返すとデータファイルが壊れてしまいました。絶対使い方を間違っていそうだ・・・
*1:string)message) ) { Close (); } } public void Close () { lock (this) { stop = true; Monitor.PulseAll (this); } } } public class App { public static void Main(string[] args) { new Server ().Run (); } public static void ListResult (ObjectSet result) { Console.WriteLine (result.Size (