Sonntag, 15. Juni 2008

Groovy and db4o

It's already a long time since my last post. Got caught in my current project.
However, recently I used some time to have a look at some topics of interest.
One of these points were Groovy. Currently, there is a lot of momentum in the area of dynamic languages. Groovy is part of it and it's really cool. I think it as a natural complementary fit to Java if used in the right way.
You can choose between dynamic and static typed programming. This freedom may be heaven and hell. However, it brings some useful and well though language constucts and features to ease programming. Just to mention a few: Closures, Everything is an object, Handling of list and maps, Proxying, Meta-class programming, construction of DSL.

The latter one is based on a groovy's builder concept.
Since I wanted to check it out, I used my favourite object oriented database db4o and built a db4o builder on top of it. It is hosted on google. You can download it here or browse the svn for the latest code.

The Groovy builder pattern is a powerful concept to provide arbitrary nested trees of objects or events, providing a tree-like façade. Though the db4o API is already very simple to use on its own, the db4o builder provides another easy way of accessing db4o databases. A Groovy builder relies on closures to setup its tree-like structure based on nodes.

Db4o has a very easy Java API that can be used in groovy as is. Open a database by and work on a Db4oContainer object to store and access objects by simply calling set and get or performing complex queries.


// Open the database
Db4oObjectContainer oc = Db4o.openFile(“d:/Db4oSampleDatabase.yap”);

// Store an object
oc.set(new Person("Doe","John") );
// query data
Query q = oc.query();
q.constrain(Person.class);
ObjectSet objectSet = q.execute();

// close it again, database operations are committed automatically
oc.close();
Well, that is already really easy.

However, in groovy you can leverage the builder concept to let it open and close the database automatically. Same when specifying a query the builder may execute the query automatically when the query has been set.

That is what the db4o builder is about.

You can do the same as above using the following syntax.


Db4oBuilder builder = new Db4oBuilder();
builder.file('d:/Db4oSampleDatabase.yap') {
set(new Person("Doe","John") )
query(Person.class) {
}
// query executed, process result
}
As you see, there is nothing special in creating a db4o builder object. You create it like any other java or groovy object. A db4o database is opened and close by using the file method (well to open a remote database server there is also a client method that takes the host, port, user name and password which should work but I haven't tested it yet).

The db4o database is opened on entering the file node and closed on leaving it. The file method is equipped with a closure to take the custom action.

Db4oBuilder builder = new Db4oBuilder();
builder.file('d:/Db4oSampleDatabase.yap') {
}
Within the closure, we now can use additional methods to store, update, delete or query objects. In order to store objects, you simply call the set method within the closure of the file method. The set method takes the object to store as argument.
Db4oBuilder builder = new Db4oBuilder();
builder.file('test.yap') {
set( new Person("Doe","John"))
}
If you need access to the opened db4o database, the db4o builder provides access to a variable called objectContainer which is of type com.db4o.ObjectContainer and represents the opened db4o database connection.
Db4oBuilder builder = new Db4oBuilder();
builder.file('test.yap') {
println objectContainer.ext().version()
}
That’s it basically. You can insert any code within the closure. For example, the following code stores 10 Person objects into the database.
Db4oBuilder builder2 = new Db4oBuilder();
builder2.file('test.yap') {
(1..10).each {
def pers = new Person("Doe"+it,"John");
set(pers)
}
}
Objects can be deleted by using the delete method.
Queries supported are based on SQL or the S.O.D.A. concept.
S.O.D.A. queries are built like a tree. A basic example
Db4oBuilder builder3 = new Db4oBuilder();
builder3.file('d:/Db4oSampleDatabase.yap') {
query() {
constrain(Person.class) {
}
}
}
The output of this query are all persons stored in the db4o database.
This query can also be simplified by using the person class as a parameter to the query node.

Db4oBuilder builder3 = new Db4oBuilder();
builder3.file('d:/Db4oSampleDatabase.yap') {
query(Person.class) {
}
}
The result of the query can be evaluated or processed either within the builder or outside of it.
Db4oBuilder builder3 = new Db4oBuilder();
builder3.file('d:/Db4oSampleDatabase.yap') {
query(Person.class) {
}
if( objectSet ) {
println ("${objectSet.size()}")
objectSet.each() { p ->
println "Person: ${p.lastName},${p.firstName},${p.age}"
}
}
}
Query results are automatically available in a variable called objectSet. Here is more complex example using or operator to combine to subqueries.
import com.db4o.groovy.Db4oBuilder;
import gk.samples.db4o.domain.Person;

Db4oBuilder builder3 = new Db4oBuilder();
builder3.file('d:/Db4oSampleDatabase.yap') {
query(Person.class) {
descend("lastName") {
constrain("KL") {
startsWith() {
or() {
descend("age") {
constrain(34) {
greater()
}
}
}
}
}
}
}
if( objectSet ) {
println "${objectSet.size()}"
objectSet.each() { p -> println "Person: ${p.lastName},${p.firstName},${p.age}" } }
}
SQL Queries are done the same way by using a special node called sqlquery. In addition to query the results after the node and outside of the builder, the query result can be processed within the sqlquery node itself as well. An example:
Db4oBuilder builder = new Db4oBuilder();
builder.file('d:/Db4oSampleDatabase.yap') {
sqlquery("from gk.samples.db4o.domain.Person") {
if( objectSet ) {
println ("${objectSet.size()}")
objectSet.each() { p -> println "Person: ${p.lastName},${p.firstName},${p.age}"}
}
}
}
Db4o allows you to setup configuration parameters. Most of them have to be set before opening the database. That is supported by the db4oBuilder by using a special configuration node.
Db4oBuilder builder = new Db4oBuilder();
builder.configuration() {
}
The configuration node sets up a reference the db4o configuration object. This reference is available by using the config variable can be use to set any db4o configuration parameter.
Db4oBuilder builder = new Db4oBuilder();
builder.configuration() {
config.activationDepth(5)
config.updateDepth(5)
config.freespace().discardSmallerThan(5)
}
Alternatively, you can pass a map of configuration parameters as argument to the configuration node. These will then be set by the db4o builder automatically.
def config = ["activationDepth" : 5, "updateDepth": 5]

builder.configuration( config ) {
}
After setting the configuration the db4o database can be opened as seen before.
builder.configuration() {
config.activationDepth(5)

file('d:/Db4oSampleDatabase.yap') {
// store or query objects
}
}
I will continue looking at Groovy and check out more concepts provided by Groovy.

1 Kommentar:

Unknown hat gesagt…

Hallo Gerd !

Ich habe gerade Deinen "User Report" bei ODBMS.ORG durchgeschmökert und bin dann auf diesen Blog-Eintrag gestossen.

Die Thematik "Groovy and db4o" ist auch für mich hochinteressant; denn ich fände es schade, beim Domain Modeling durch OR-Restriktionen behindert zu werden.

Bezüglich db4o habe ich allerdings einen wichtigen Vorbehalt: Fehlende referentielle Integrität ! Du schreibst: "Objects can be deleted by using the delete method." Aber ob sogenanntes kaskadierendes Löschen von db4o unterstützt wird, bleibt offen. Was also sind Deine Erfahrungen bezüglich "db4o und Löschen" ? Unterstützt db4o inzwischen referentielle Integrität ?

Das soll als Auftaktkommentar erstmal reichen !

Tschüss

Kai aus Paderborn an der Pader