db4o: Transactions
So I showed the basics, explained the activation-system and pointed out how db4o manages object-identities. So far so good, but I’ve never explain a very important feature of most databases, the transactions. Time to catch up.
(All posts of this series: the basics, activation, object-identity, transactions, persistent classes, single container concurrency, Queries in Java, C# 2.0, client-server concurrency, transparent persistence, adhoc query tools)
Transactions
Actually we never used transaction so far, right? So it’s like auto commit? Lets try it out. First we run a little application which stores a object. After that the application goes into an endless loop. What do you do with application which hangs like this? Right, kill that bastard. So we kill our application with the Task-Manager or the Visual-Studio-Debugger.
File.Delete("database.db4o"); using (var db = StartDataBase()) { db.Store(new SimpleObject("A New Object")); while (true) { Console.Out.WriteLine("Demo: Kill this process now!! (Yay, Ctrl Alt Delete"); Console.Read(); } }
So our demo-application was terminated abnormally. So let’s check what is in our database.
using (var db = StartDataBase()) { var objects = (from SimpleObject o in db select o); AssertEquals(1, objects.Count()); }
Surprise, surprise, the test fails. Nothing is in the database. The reason is simple. Db4o automatically creates a transaction for you. As soon as you open the object-container a transaction is started. This transaction has the ACID-properties (Wikipedia, db4o) like other databases.
Controlling The Transaction
Well it’s wonderful that db4o creates the transaction by default. But it would be pretty useless if you cannot control it. Of course you can do that. The IObjectContainer interface has two methods for this: Commit()- and Rollback(). When you call those, the transaction is committed or rolled back and a new transaction is started immediately . For example:
File.Delete("database.db4o"); var db = StartDataBase(); try { db.Store(new SimpleObject("Expect-To-Be-Committed")); db.Commit(); db.Store(new SimpleObject("Expect-To-Be-Not-Committed")); throw new SomethingWentWrongException(); } catch(SomethingWentWrongException) { db.Rollback(); } finally { db.Dispose(); } using (var checkDB = StartDataBase()) { var exists = (from SimpleObject o in checkDB where o.Content.Equals("Expect-To-Be-Committed") select o); AssertEquals(1, exists.Count()); var wasNotCommited = (from SimpleObject o in checkDB where o.Content.Equals("Expect-To-Be-Not-Committed") select o); AssertEquals(0, wasNotCommited.Count()); }
So this works fine. By the way, the Close()- and Dispose()-methods call Commit() implicitly, so with the C# using-statement you commit automatically.
Personally I use a wrapper around the transaction handling. It takes a closure which contains the db-operations and ensures that everything runs through or everything is rolled back:
dataBase.InTransaction(
tx=>{
db.Store(new DemoObject());
});
Don’t Let Your Objects Fall Behind
Now to a really nasty detail of the db4o-transaction-handling. We retrieve a object and change a property. Then we update this object in the database with Store(). After that we rollback the transaction. The demo-code:
using(var db = StartDataBase()) { var single = (from SimpleObject o in db select o).Single(); single.Content = "New Value"; db.Store(single); db.Rollback(); AssertEquals("Old-Value", single.Content); }
Unfortunately this test will fail =(. When you rollback a transaction your objects in memory aren’t rolled back automatically. So you have to be very careful when try to resume after a some kind of error. So if you want to set a object in the state of database you can use Refresh():
db.Rollback();
db.Ext().Refresh(single,int.MaxValue);
Resume-Strategy
Now we know how we can commit and rollback a transaction. Also we know that when we rollback a transaction, the objects in memory keep their state. So when you want to resume after a rollback, you have to have some kind of strategy. This strategy heavily depends on your use case.
Isolation
In a client-server-model the isolation guarantees are important. Db4o has read-committed isolation properties.
Next time
So far I’ve showed some basics of db4o. Of course this is only the tip of the iceberg. You can read up all the details in the documentation. So next time I give some tips on how to model your persisted classes.
- db4o: Object-Identity and High-Level-Caching
- Rx-Framework, Lombok & Music For Everyone
I’d like to see what the InTransaction function would look like ?
Thanks.
What the InTransaction function looks like is up to you. A simple implementation can be a extension-method for the IObjectContainer which runs the closue and then commits on the container (Like here: https://gist.github.com/976768) . In a real world application the InTransaction function may does more. And probably you also use you own class instead of the object container directly.
Nice work!!!!