|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
ContentsIntroductionIn part 1 of this article I discussed how to perform CRUD operations with db4o. (If you have not read through part 1, I suggest you to do so to learn the basics of db4o as well as understand the object model used in the code samples.) In this part, I will explore other features of db4o that are useful in multi-threading environment and n-tier applications. These include client-server feature and transaction & concurrency support. Before we start, notice that the development version of db4o 7.2 (which can be downloaded here) and C# 3.0 are used for all code samples. Client-Server DeploymentIn part 1, we’ve seen the simplest usages of db4o database, i.e. the stand-alone mode in which we create an instance of type In the embedded server mode, a single instance of type IObjectServer server = null;
try
{
// Create the server instance
server = Db4oFactory.OpenServer(DB_PATH, 0);
// Create the first client via the server instance
using (IObjectContainer client1 = server.OpenClient())
{
Line line = client1.Query
The above code shows that we can have multiple clients accessing the same database file. The second parameter of To make the interaction between the two clients easy to understand, I just use one thread to launch the two clients. In real-life applications, it’s likely that you will create multiple threads each of which is responsible for one or more database clients. (In fact, if there’s no such use case then you should use the standalone mode of db4o instead of the embedded mode.) Networking ModeIf your database is deployed in a different process or machine from the database clients, you would need to use db4o’s networking mode instead. There are a few differences when using this mode with the embedded server mode:
Simple enough, let’s look at some code IObjectServer server = null;
try
{
// Create a server socket and have it listen on port 9999
server = Db4oFactory.OpenServer(DB_PATH, 9999);
// Specify some permitted credentials
server.GrantAccess("buuid", "buupass");
server.GrantAccess("somebody", "somepassword");
try
{
// Try to connect using the invalid credential
IObjectContainer client = Db4oFactory.OpenClient("localhost", 9999, "buuid", "any");
Assert.Fail("Wrong password, must not go here");
}
catch (Exception e)
{
Assert.IsTrue(e is InvalidPasswordException);
}
using (IObjectContainer client1 = Db4oFactory.OpenClient("localhost", 9999, "buuid", "buupass"))
using (IObjectContainer client2 = Db4oFactory.OpenClient("localhost", 9999, "somebody", "somepassword"))
{
client1.Store(new Line(new CPoint(0, 0), new CPoint(10, 10), Color.YellowGreen));
client1.Commit();
// client2 will see the change
Assert.AreEqual(
1,
( from Line line in client2
where line.Color == Color.YellowGreen
select line
).Count()
);
}
}
finally
{
if (server != null)
server.Close();
}
Again, the code is much simplified by having the server and its two clients run on the same thread. If you move the server to another process or machine then the behavior will remain unchanged. The only other point of interest in the code is that the query is written in LINQ just to show off db4o’s LINQ provider which is available in db4o 7.2. Out-of-Band SignalingIn the networking mode, sometimes you will want the client to be able to request the server to perform an arbitrary data-related action not existing in any In order to have the server respond to a custom message from the client, you simply need to write a class implementing the Below is a sample code which does just that. [TestClass]
public class OutOfBandTest : IMessageRecipient
{
public const string DB_PATH = "mypaintdb.db";
IObjectServer server = null;
[TestMethod]
public void TestCloseServer()
{
// Start the server and set initialize message recipient
server = Db4oFactory.OpenServer(DB_PATH, 9999);
server.GrantAccess("buuid", "buupass");
server.Ext().Configure().ClientServer().SetMessageRecipient(this);
using (IObjectContainer client = Db4oFactory.OpenClient("localhost", 9999, "buuid", "buupass"))
{
IMessageSender sender = client.Ext().Configure().ClientServer().GetMessageSender();
// Send the shutdown message to server
sender.Send(new ShutdownServerMessage());
try
{
client.Store(new Line(null, null));
Assert.Fail("Server is already shutdown. Must not go here.");
}
catch (Exception e)
{
Assert.IsTrue(e is DatabaseClosedException);
}
}
}
public void ProcessMessage(IMessageContext context, object message)
{
if (server != null && message is ShutdownServerMessage)
{
server.Close();
}
}
class ShutdownServerMessage { }
}
That’s it for working with client-server features of db4o. Let’s now discuss about db4o’s support for transaction. Transaction and ConcurrencyTransaction SupportSo far, in most of our examples, we have used db4o’s implicit transaction which automatically starts a new transaction whenever an object container is created via Now, suppose we need to write a function to change the color all of existing line shapes in our database and we need to assure that this operation is done atomically, i.e. either all lines are updated or no line is; explicit transaction management would be absolutely necessary here. Let's look at the code that does just that. IObjectContainer container = null;
try
{
container = Db4oFactory.OpenFile(DB_PATH);
IObjectSet set = container.QueryByExample(typeof(Line));
Assert.IsTrue(set.Count > 0);
// Change color of all lines
for (var i = 0; i < set.Count; i++)
{
((Line)set[i]).Color = Color.Purple;
container.Store(set[i]);
// After storing 2 lines, deliberately fail
if (i == 1)
{
throw new Exception();
}
}
container.Commit();
}
catch
{
container.Rollback();
// Strange enough, this asserts to true...
Assert.AreEqual(
2,
container.Query
You can see that I deliberately throw an exception after two lines have their colors changed. Interestingly, the assertion code after the rollback indicates that there are actually two lines having their color changed. Isn’t this unexpected because the rollback is supposed to wipe out all changes in the transaction? Well, remember in part 1 of this article, I mentioned about the db4o’s object cache which maintains references to all objects stored or retrieved in the life-cycle of an object container. Back to our example, when the container fetches the list of line shapes from the database, it notices that some lines already existing in its cache and returns those cached references instead of creating and returning new instances. In other words, while the rollback operation is successful and the database is updated properly with the wipe-out (otherwise, we would receive an exception), the returned objects are those having been in memory instead of new instances created from data retrieved from the database. To retrieve the database copies, we can simply perform the query in a new container as you can see in the code sample. An alternative is to clear the cache by invoking So that we have seen how to explicitly use Db4o provides semaphores to allow developers to control access to critical sections of the application code. A semaphore is identified by a name and can be acquired by an object container by invoking
Now, let’s write a client-server code which has two clients, one retrieves all Line shapes and try to change their color and another client running on another thread tries to update the color of a specific line. Let’s assume there is a requirement that IObjectServer server = null;
try
{
server = Db4oFactory.OpenServer(DB_PATH, 9999);
server.GrantAccess("buuid", "buupass");
// This client updates the color of all lines
Thread clientThread1 = new Thread(delegate()
{
using (IObjectContainer client1 = Db4oFactory.OpenClient("localhost", 9999, "buuid", "buupass"))
{
// Acquire the semaphore
if (client1.Ext().SetSemaphore(LOCK_KEY, 0))
{
IObjectSet set = client1.QueryByExample(typeof(Line));
foreach (Line line in set)
{
line.Color = Color.RosyBrown;
client1.Store(line);
Thread.Sleep(500);
}
}
}
});
// This client tries to delete the first line
Thread clientThread2 = new Thread(delegate()
{
using (IObjectContainer client2 = Db4oFactory.OpenClient("localhost", 9999, "buuid", "buupass"))
{
// Cannot acquire semaphore with this call
Assert.IsFalse(client2.Ext().SetSemaphore(LOCK_KEY, 0));
// After 5 seconds, client1 already releases the lock (there're only 4 Lines in the DB)
// thus, this call would succeed
Assert.IsTrue(client2.Ext().SetSemaphore(LOCK_KEY, 5000));
// Test another call - pass because client2 is already the lock owner
if (client2.Ext().SetSemaphore(LOCK_KEY, 0))
{
IObjectSet set = client2.QueryByExample(typeof(Line));
((Line)set[0]).Color = Color.Black;
client2.Store(set[0]);
}
}
});
// Start client 1, then sleep a bit to make sure the semaphore is acquired
// before starting client 2
clientThread1.Start();
Thread.Sleep(1000);
clientThread2.Start();
// Wait for the threads to terminate...
clientThread1.Join();
clientThread2.Join();
}
finally
{
if (server != null)
server.Close();
}
Acute readers will ask, “Hey, ConclusionWe have discussed through the client-server feature and transaction & concurrency support in db4o. This is not an end though, since db4o still has so many interesting features that are worth looking at. However the two features introduced in this article, together with the knowledge presented in part 1, should provide you with enough information to use db4o in more advanced scenarios than just in standalone or embedded applications. I hope you enjoy reading the article! Resources
|
|||||||||||||||||||||||||||||||||||