Friday, January 4, 2013

Using DB4o

db4o is an excellent Database Management System, it's a fresh breath of air to us relational database users. For years we have been battling with Object/Relational impedance mismatches. And I am not about revive an age old debate here, but I have been giving this some serious thought ever since our last client rejected using a really popular ORM outright(sadly I think you know what it is), in his words "it's too slow". A little part of me died that day. His solution, a home grown framework, a quite amazing one at that. Maintainability was a little price to pay for much sought after performance gains, it wasn't too difficult too once you get the hang of it.

I digressed a little, apologies, anyways getting back to db4o. What I love about it is it's simplicity. Ok lets talk basic necessities, what do you need to get this baby up and running, a cuppa coffee or a can of redbull, you sit right down after a sip, get your data model pinned down. And that's it. Yeah that really is it. There's no more complex mappings, annotations or configurations. It really is that simple.

Lets talk querying, db4o offers three types of querying techniques, There's Query By Example(QBE), Native Queries(NQ) and SODA Query API. For a newbie QBE offers the perfect solution, its pretty much the same as Hibernate, apart from a few trivial differences you can totally wrap your head around. Native queries are even easier, they offer type safety, compile-time checks and refactorability, how cool is that. SODA Query API is db4o's low level querying API,it's pretty verbose for my liking. 

db4o is perfect for freely distributed applications under proper licences, although stepping off this boundary would cost you. How much you ask, well it depends. Is it worth it, well it depends too, but that's a topic best left for another post, if the suspense is killing you, http://www.db4o.com/ offers very compelling reasons why you should.

Let's get dirty with a sample application then, apologies in advance for my data model, I was merely testing this out in an actual application and too lazy to whip up a quick starter pack.

Ok then, here's the maven dependency you need, now there are a few complaints about the unavailability of this particular repository (and yes you won't find this in the default maven repository) although I haven't personally been at the receiving end of this. You need to add the following to your maven configuration.


<repositories>
 <repository>
  <id>source.db4o</id>
  <url>http://source.db4o.com/maven/</url>
 </repository>
</repositories>

And now we are all set to resolve the dependency.

<dependency>
 <groupId>com.db4o</groupId>
 <artifactId>db4o-core-java5</artifactId>
 <version>8.1-SNAPSHOT</version>
</dependency>

As for the data model, now this is a model class I created to persist image downloads be warned as it's simplicity might astound you.

public class DownloadImage{
 public String imageUrl;
 public DownloadImage(String imageUrl) {
  this.imageUrl = imageUrl;
 }

 public String getImageUrl() {
  return imageUrl;
 }
}

Here comes the heavy works, now it's best if you keep the connection awake/open as long as possible, why you ask, simples, for performance, when you open and close connections, db4o has to reload data, recreate class metadata/structures etc etc there's a lot of re-'s, although these are standard procedures and hence fast, you don't need this additional overhead, you opted to use db4o for its performance gains after all right. Right.

Now in the code to follow I am using the singleton pattern and my ObjectContainer (definition to follow) is created/opened only once, and it comes with a neat built-in shutdown hook, so I thought what the heck. 


import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.config.EmbeddedConfiguration;
import com.db4o.constraints.UniqueFieldValueConstraint;
import com.kohls.strategies.DownloadImage;

public class DatabaseConnection {
 private DatabaseConnection() {

 }

 private static final ObjectContainer OBJECT_CONTAINER = Db4oEmbedded
   .openFile(Configuration.getConfig(), PropertyLoader.getDbLocation());

 private static class Configuration {
  private static EmbeddedConfiguration getConfig() {
   EmbeddedConfiguration CONFIGURATION = Db4oEmbedded
     .newConfiguration();
   CONFIGURATION.common().objectClass(DownloadImage.class)
     .objectField("imageUrl").indexed(true);
   CONFIGURATION.common().add(new UniqueFieldValueConstraint(DownloadImage.class,"imageUrl"));
   return CONFIGURATION;
  }
 }

 public static ObjectContainer getInstance() {
  return OBJECT_CONTAINER;
 }

 private Object readResolve() {
  // Return the one true instance and let the garbage collector
  // take care of the impersonator.
  return OBJECT_CONTAINER;
 }
}

Hmm now what's that shifty bit there with the private static class, well by default db4o won't check for uniqueness of your records, hence you can store multiple, yeah multiple identical records. To avoid that you need to add constraints. But no, this alone won't guarantee uniqueness. You need to be little clever and put additional checks in place during persistence, a small price to pay if you ask me. 

At this point I think I should tell you what an Object Container is. An Object Container represents your database and all operations would be executed using this. You can have multiple Object Containers.

And finally for the DAO class, cleverly disguised as a service class, I dropped the backing interface definition for this for simplicity.



import java.util.Collection;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.constraints.UniqueFieldValueConstraintViolationException;
import com.kohls.service.DataService;
import com.kohls.strategies.DownloadImage;
import com.kohls.utils.DatabaseConnection;

public class DataServiceImpl {
 private ObjectContainer db = null;

 public void addDownloadable(DownloadImage downloadable) {
  try {
   db = DatabaseConnection.getInstance();
   db.store(downloadable);
   db.commit();
  } catch (UniqueFieldValueConstraintViolationException e) {
   db.rollback();
  } finally {
  }
 }

 public Collection<DownloadImage> getDownloadables() {
  ObjectSet<DownloadImage> downloadables = null;
  try {
   db = DatabaseConnection.getInstance();
   downloadables = db.queryByExample(new DownloadImage(null));
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
  }
  return downloadables;
 }

 public Collection<DownloadImage> getDownloadableByUrl(String url) {
  ObjectSet<DownloadImage> downloadables = null;
  try {
   db = DatabaseConnection.getInstance();
   downloadables = db.queryByExample(new DownloadImage(url));
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
  }
  return downloadables;
 }

 public DownloadImageupdateDownloadable(DownloadImage downloadable) {
  ObjectSet<DownloadImage> result = null;
  DownloadImagefound = null;
  try {
   db = DatabaseConnection.getInstance();
   result = db.queryByExample(downloadable);
   found = result.next();
   //do something with it
   db.commit();
  } catch (Exception e) {
   e.printStackTrace();
  }
  return found;
 }

 public void delete(DownloadImage downloadable) {
  ObjectSet<DownloadImage> result = null;
  DownloadImagefound = null;
  try {
   db = DatabaseConnection.getInstance();
   result = db.queryByExample(downloadable);
   found = result.next();
   db.delete(found);
   db.commit();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

Every operation happens in a transaction hence the explicit commits. Apart from this , explicit commits also allow you to capture exceptions, allowing you to, yeah you guessed it, rollback. It's good practice when you are storing or deleting records, to grab the record in question first.

Ok that's all I have time for.

No comments:

Post a Comment