German has posted 18 posts at DZone. View Full User Profile

db4o: Simple POJO Persistence

03.05.2010
| 19757 views |
  • submit to reddit

Ranging from mobile to web applications, and from plain old Java to Scala or Groovy dialects, a modern Java developer always needs an ace in the hole when it comes to dealing with data persistence. Ideally, you're looking for a solution that gives you enough power to handle your domain complexity while being simple enough to boost your productivity by avoiding painful configurations or steep learning curves.

On one side of this scenario you have relational databases with ORM tools which force you to go through several steps like the creation of an object mapping file, a database configuration file, a helper class to initialize a session factory and class association mappings (i.e. precious time spent on downloading, installing, configuring, and then writing a lot of XML). On the other side there's just plain old serialization (mostly ending up in XML files). But with Java serialization you quickly run into a critical caveat: objects must be marked by implementing the java.io.Serializable interface. However, just adding "implements Serializable" to a class definition doesn't automatically make it serializable. The instance variables of the class must also be serializable (bummer). If this is not the case and you try to serialize the class an exception would be thrown. Unfortunately serialization in Java is not a straight forward solution for object persistence. There are many additional pitfalls associated with the serialization process such as object versioning problems, breaking of object identity and schema evolution issues (among others).

Right in the middle, being powerful enough to handle complex domains but so simple that you can actually store objects with one line of code, there's db4o, a persistence engine that was conceived to make the Java developer's life easier. Why define a separate schema for your data if your classes define it perfectly? With db4o your object model is your database schema. When you define the structure of your classes you're not only making a statement about how your objects should be run but also about how they should be made persistent! And why settle with object serialization? Serialization is a popular alternative to avoid complex persistence settings but can handle a very limited number of simple scenarios. 

So, what if you could have the power of an ACID database with the simplicity of use of object serialization?

In this article I will show you how to leverage the different features of db4o in different Java based scenarios ranging from mobile to enterprise applications with a focus on real samples taken from real db4o based projects.

Using db4o in the Griffon framework

Griffon is a Grails like application framework for developing desktop applications in Groovy. A db4o plugin (griffon-db4o v0.1) enables lightweight access to database functionality using db4o.Upon installation the plugin generates the files Db4oConfig.groovy (that contains the datasource definition) and BootstrapDb4o.groovy (which defines init/destroy hooks for data to be manipulated during app startup/shutdown). Finally a new dynamic method named withDb4o is injected into all controllers, giving you access to a com.db4o.ObjectContainer instance, with which you'll be able to make calls to the database.
Consider the class:
class Person { 
        int id
        String name
        String lastname
}
Bootstrapping data could be filled in with this simple code:
class BootstrapDb4o {
def init = { db4o ->
db4o.store(new Person(id: 1, name: "Danno", lastname: "Ferrin"))
db4o.store(new Person(id: 2, name: "Andres", lastname: "Almiray"))
db4o.store(new Person(id: 3, name: "James", lastname: "Williams"))
db4o.store(new Person(id: 4, name: "Guillaume", lastname: "Laforge"))
db4o.store(new Person(id: 5, name: "Jim", lastname: "Shingler"))
db4o.store(new Person(id: 6, name: "Josh", lastname: "Reed"))
db4o.store(new Person(id: 7, name: "Hamlet", lastname: "D'Arcy"))
}

def destroy = { db4o ->
}
}

Implementing a controller that will react to an application event, load the data into a temporal List and update model.personsList inside the EDT is as simple as this:

class SampleController {
def model

def onStartupEnd = { app ->
withDb4o { db4o ->
def tmpList = db4o.query(Person)
edt { model.personsList.addAll(tmpList) }
}
}
}

db4o and Scala

Not surprisingly db4o is a perfect fit for Scala. It's so straight forward that you can implement master/slave replication of persistent objects in under 100 lines of Scala code. The code to bring the slave up-to-date comes down to this:

private def bringSlaveUpToDate() {
   println("starting replication...")
   
   val replication = Replication.begin(master, slave, replicationListener)
   
   val changes = replication.providerA().objectsChangedSinceLastReplication().iterator()
   if (!changes.hasNext) {
     println("Nothing to replicate")
     return
   }
   
   while (changes.hasNext()) {
     val changed = changes.next();
     replication.replicate(changed)
   }
  
   removeCommitListener
   try {
     replication.commit()
   } finally {
     listenToMasterCommits
   }
   
   println("replication finished")
 }
And usage of this Scala replication agent is amazingly simple:
package instant;

import com.db4o._
import com.db4o.config._
import scala.actors.Actor._
import scala.concurrent.SyncVar

case class Item(name: String)

object Main {
 
  def main(args : Array[String]) : Unit = {
    
    deleteFile("master.db4o"); deleteFile("slave.db4o")
    
    val slave = Db4o.openFile(configuration, "slave.db4o")
    val masterServer = Db4o.openServer(configuration, "master.db4o", -1)
    val masterClient = masterServer.openClient(configuration)
    for (i <- 1 to 10) {
      masterClient.store(Item("Item " + i))
    }
    masterClient.commit()
    
    try {
      
      val agent = new ReplicationAgent(masterServer.ext.objectContainer, slave)
      agent.start()
      
      val finished = new SyncVar[boolean]
      
      val masterUser = actor {
        for (i <- 10 to 150) {
          val item = Item("Item " + i)
          masterClient.store(item)
          
          println("Committing " + item)
          masterClient.commit()
        }
        finished.set(true)
      }
      
      finished.get
      
      println("stopping agent")
      agent.stop
      
    } finally {
      withErrorHandling { masterServer.close }
      withErrorHandling { slave.close }
    }
  }
 
  def withErrorHandling(block : => Unit) {
    try {
      block
    } catch {
      case e => println(e.toString)
    }
  }
 
  def configuration = {
    val c = Db4o.newConfiguration()
    c.generateUUIDs(ConfigScope.GLOBALLY)
    c.generateVersionNumbers(ConfigScope.GLOBALLY)
    c
  }
 
  def deleteFile(fname: String) = new java.io.File(fname).delete()
}
Servlet with db4o

db4o can also be used in a classic Java web environment because the database works in thread-safe mode. Servlets can be accessed by multiple threads since transactions in db4o work in a thread-safe manner.

First you start by copying the db4o library to the WEB-INF/lib directory. This allows your application to access the class responsible for the creation of server and client. Then you need to configure access to the database file and provide the means to create an ObjectServer for each application. One great way to do this consists in creating the ObjectServer in the application context startup and close the connection when the context has ended. We can do this by elaborating a Listener class:
package myproject.dao;   
     
    import javax.servlet.ServletContext;   
    import javax.servlet.ServletContextEvent;   
    import javax.servlet.ServletContextListener;   
    import com.db4o.Db4o;   
    import com.db4o.ObjectServer;   
     
   public class Db4oServletContextListener implements ServletContextListener {   
      public static final String KEY_DB4O_FILE_NAME = "db4oFileName";   
    
      public static final String KEY_DB4O_SERVER = "db4oServer";   
    
      private ObjectServer server = null;   
    
      public void contextInitialized(ServletContextEvent event) {   
         close();   
         ServletContext context = event.getServletContext();   
         String filePath = context.getRealPath("WEB-INF/db/" + context.getInitParameter(KEY_DB4O_FILE_NAME));   
         server = Db4o.openServer(filePath, 0);   
         context.setAttribute(KEY_DB4O_SERVER, server);   
         context.log("db4o startup on " + filePath);   
      }   
    
      public void contextDestroyed(ServletContextEvent event) {   
         ServletContext context = event.getServletContext();   
         context.removeAttribute(KEY_DB4O_SERVER);   
         close();   
         context.log("db4o shutdown");   
      }   
    
      private void close() {   
         if (server != null) {   
            server.close();   
         }   
         server = null;   
      }  
   }  
It is very easy to understand the code above. When the context is started a db4oServer is instantiated. And when stopped the connection with the database is ended. Don't forget to configure the application context Listener in the web.xml file present in WEB-INF directory:
<listener>  
      <listener-class>meuprojeto.dao.Db4oServletContextListener</listener-class>  
</listener> 
Note that you have to provide the full name of the Listener class (including the package). Finally it's time to configure the db4o file path used by the Listener:
<context-param>  
      <param-name>db4oFileName</param-name>  
      <param-value>web.yap</param-value>  
</context-param>  
Android persistence: db4o

When you consider SQLite, the defacto database for Android, and compare it with db4o you'll realize that there are many benefits when using object persistence in your mobile application: easier code maintenance due to simplicity, and the ability to create a variety of new, innovative applications based on more complex data models. Unlike in rigid, predefined SQL tables, db4o allows the storage of dynamic, free-form data, which can be changed or amended any time (which is specially useful when sending updates to your users). In addition, db4o allows for efficient data replication with its db4o Replication System (dRS), another missing element in Android's software stack.

Let's consider an insert and update operation with SQLite on Android:
//SQLite Insert
public void addPassword(PassEntry entry) {
    ContentValues initialValues = new ContentValues();
    initialValues.put("password", entry.password);
    initialValues.put("description", entry.description);
    initialValues.put("username", entry.username);
    initialValues.put("website", entry.website);
    initialValues.put("note", entry.note);
    db.insert(DATABASE_TABLE, null, initialValues);
}

//SQLite Update
public void updatePassword(long Id, PassEntry entry) {
    ContentValues args = new ContentValues();
    args.put("password", entry.password);
    args.put("description", entry.description);
    args.put("username", entry.username);
    args.put("website", entry.website);
    args.put("note", entry.note);
    db.update(DATABASE_TABLE, args,"id=" + Id, null);
}
If you choose db4o you can get rid of the code above and replace it with this method:
//db4o Upsert
public void savePassword(PassEntry entry) {
    if(entry.id == 0)
        entry.id = getNextId();
    db().store(entry);
    db().commit();
}
which serves as both insert and update (store acts as update if the object already exists and that's determined automatically by db4o).

db4o Spring integration

Want to use db4o for lightweight object persistence in your spring based application? Try a db4o extension that has been around for some time, the 'spring-db4o' module from Spring Modules. It is quite useful for anyone integrating db4o into an enterprise application to benefit from declarative transaction management, exception translation, and ease of configuration.

Configuration is pretty straight forward. In order to create a memory based db4o ObjectContainer, the following configuration can be used:

<bean id="memoryContainer" class="org.db4ospring.ObjectContainerFactoryBean">
   <property name="memoryFile">
     <bean class="com.db4o.ext.MemoryFile"/>
   </property>
</bean>

For an ObjectContainer connected to a (remote) server:

<bean id="remoteServerContainer" class="org.db4ospring.ObjectContainerFactoryBean">
   <property name="hostName" value="localhost"/>
   <property name="port" value="123"/>
   <property name="user" value="foo"/>
   <property name="password" value="bar"/>
</bean>

While creating a database file based, local ObjectContainer can be achieved using a bean definition such as:

<bean id="fileContainer" class="org.db4ospring.ObjectContainerFactoryBean">
   <property name="databaseFile" value="classpath:db4o-file.db"/>
</bean>
The core classes of db4o module that are used in practice, are Db4oTemplate and Db4oCallback . The template translates db4o exceptions into Spring Data Access exception hierarchy (making it easy to integrate db4o with other persistence frameworks supported by Spring) and maps most of db4o's ObjectContainer and ExtObjectContainer interface methods, allowing one-liners:
db4oTemplate.activate(personObject, 4); // or
db4oTemplate.releaseSemaphore("myLock");
The db4o module also provides integration with Spring's excellent transaction support through Db4oTransactionManager class. Since db4o statements are always executed inside a transaction, Spring transaction demarcation can be used for committing or rolling back the running transaction at certain points during the execution flow.

The bottom line

Many Java developers are already enjoying the simplicity of object persistence with db4o. Before choosing a relational datastore with ORM mapping or XML serialization for your next project ask yourself whether you can make things simpler and faster by just using db4o. Take db4o for a ride, you won't regret it!
Published at DZone with permission of its author, German Viscuso.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Robert Gerling replied on Fri, 2010/03/05 - 4:49pm

Nice article. I stumbled upon db4o a few times during reading posts here at Javalobby, but unfortunately haven't tried it yet. Two questions raised in my mind after reading your post: What about refactoring? I mean, what will  happen to instances of class "A" already stored in the database, when you will refactor your class "A" POJO? The other question is how put db4o to work on the Android Platform? I assume it is not part of any Android version, so you have to deploy the db4o database with your Android application somehow?!. That raises another question concerning performance of the db4o database on an device with such limited resources as an Android mobile phone.

I guess i could find the answers to all my questions by investing a little time searching the web, but i thought you might already know the answers or at least can point me in some directions. I'm looking forward to your response.  

Robert Greene replied on Sat, 2010/03/06 - 1:09am

Most of the refactoring works without impact to your existing data in the database. ( e.g. adding removing attributes, renaming things, adding new leaf classes , etc ) That is one of the really cool things about db4o. If you are doing things like changing types, then there are also call backs which you can implement to do the conversions for you at runtime as you eventually access the old objects. Only time things get harry is when you try to do very disruptive things like reroot an inheritance hierarchy, etc. In those cases, you have to write some code to move your existing objects into the new model and then delete them.

Give it a try .... db4o IS The Database for Developers!
Cheers,
-Robert
Versant Corp - The db4o team

German Viscuso replied on Mon, 2010/03/08 - 5:25am in response to: Robert Gerling

Hi mssimor, thanks a lot.

Let me point you in the right direction:

db4o works well on Android, performance is good. Of course you'll have to add the jar file to your Android project. For more info see the Android section on developer.db4o.com where you'll find a link to MapMe, a db4o based app available on the Android Market including source code. You might also want to take a look at the Android section on the db4o docs.

With regards to refactoring, db4o handles some scenarios automatically (eg. adding a member to a class). Please take a look at the refactoring page on the db4o docs

I also would like to add that db4o is compatible with several open source licenses (not only the GPL) via the dOCL.

Best!

German

Robert Gerling replied on Mon, 2010/03/08 - 3:26pm in response to: German Viscuso

Thank you, German.

I'll have a look at the posted links.

 Regards, Robert

German Viscuso replied on Wed, 2010/03/10 - 6:42am

This article is mentioned in this developer podcast (minute 8):

http://techcast.chariotsolutions.com/index.php?post_id=590881

German Viscuso replied on Thu, 2010/03/11 - 10:51am

Here's a nice thread about a refactoring question (on Stackoverflow):

http://stackoverflow.com/questions/2420248/handling-data-maintenance-in-object-databases-like-db4o

Joe Cool replied on Thu, 2010/05/13 - 4:46pm

Thanks for the article!

I tried to integrate db4o into my existing spring-3.0.0 application to no avail.

spring-modules are pretty old, they have not been updated in 2 years so they depend on db4o:6.1 which cannot be downloaded from any maven-repo:

<dependency>
  <groupId>org.springmodules</groupId>
  <artifactId>spring-modules-db4o</artifactId>
  <version>0.8a</version>
</dependency>

Even if it was possible to download it manually this would mean I would have to use an old version of db4o?

Regards, getagrip

 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.