Recently, I’ve been looking at db4o – an open source object database. I have plenty of experience with various object relational mapping frameworks, but wanted to try an alternative framework, one which should result in clean code, with no need to have the performance hit, and configuration complexity often associated with ORM.
As others have noted, db4o seems like a good fit with Scala. Hopefully the combination of the two should result in a more maintainable codebase.
I will show a couple of examples of the db4o API here. First I need to create a simple class that I will persist, and later query. For this I have created a Person class, that has only one field, a name, well I did say it was a simple class.
class Person(val name: String)
Opening a connection to the database, and persisting an instance of our Person class can be achieved with a few lines of code, as shown below:
val db = Db4o openFile "test.yap"
val me = new Person(“Matthew”)
db set me
This will open, or create a new file based database called “test.yap” in the current directory and persist the created instance of Person. Persisting objects really is as simple as that, no schema or mappings to define. The set method can be used to store changes to existing objects, as well as storing new objects.
The next obvious step is retrieving any objects we have persisted. Db4o offers three ways to do this:
- Query by example – This is a simple form of querying , one that can be found in most ORM frameworks too, and provides a way to query the database based on a ‘template’ instance of a class. All persisted instances matching this template will be returned.
- Native queries – I would imagine that most queries would be written this way. Native queries provide a powerful way to write queries against the database in code, providing a great typesafe way to write queries.
- SODA query API – SODA is db4o’s internal query system. You wouldn’t normally need to write a query using SODA unless there were performance issues when using one of the other two query types. All types of queries actually get translated into a SODA query by the framework.
I will show an example of a native query here, as I believe this will be the more common type of query used. Writing a query class involves implementing the match method on the Predicate interface. This method takes one parameter, an instance of the class being queried, and returns a boolean – true if this query should return the given instance.
Thanfully Scala allows us to make the writing of Predicates even easier through the use of an implicit conversion. We can convert a function that takes an instance of our person class and returns a boolean into a Predicate quite easily. An example of a simple query, is shown below:
implicit def toPredicate[T](predicate: T => Boolean) =
new Predicate[T]() {def `match`(entry: T): Boolean = {predicate(entry)}}
val result = db query {person: Person => person.name.contains("t") }
The example above shows our implicit conversion that allows for easy creation of (typesafe) queries, as well as a simple query that will return every Person persisted that has a letter ‘t’ in their name.
The query method returns an ObjectSet, a db4o class that can be used to iterate over every matching instance. This ObjectSet won’t play nice with Scala’s ‘for’ loop. Thankfully conversion of the ObjectSet into an iterable object can be easily achieved as shown below:
class RichObjectSet[T](objectSet:ObjectSet[T]) extends Iterator[T] {
def hasNext:Boolean = objectSet.hasNext()
def next:T = objectSet.next()
}
implicit def toRichObjectSet[T](objectSet: ObjectSet[T] ) =
new RichObjectSet[T](objectSet)
Executing our original query and displaying the results can now be performed in a couple of lines of code as shown below:
val result = db query {person: Person => person.name.contains("t") }
for(person <- result) println(person.name)
So, as you can see persisting objects as well as running queries can be achieved with only a few lines of code.
Seeing this code, you may question the efficiency of the query. Surely db4o can’t be passing every persisted object to our match method. This would be extremely inefficient. As I hinted at previously, db4o will attempt to convert our query into it’s own internal SODA representation before executing. It achieves this transformation by examining the bytecode of our query before executing. If this translation to SODA fails, only then will db4o resort to calling our match method with every instance of our class we have persisted.
Unfortunately for my experiments in using db4o with Scala, the query optimiser does not seem to cope with the bytecode generated by the Scala compiler well, resulting in most queries failing to get converted to SODA. I am currently investigating ways in which the query optimiser can be made to work with Scala, and will post my findings here.
I hope this has given an insight into how object persistence and retrieval can be achieved in a typesafe manner in a few lines of code, with no complex mapping required. I think the simplicity of the API that db4o provides is certainly a good match with Scala and if the query optimisation issue can be solved, then one that is worth looking at further.