db4o: Simple POJO Persistence
Join the DZone community and get the full member experience.
Join For FreeRanging 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 frameworkConsider the class:
class Person {Bootstrapping data could be filled in with this simple code:
int id
String name
String lastname
}
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() {And usage of this Scala replication agent is amazingly simple:
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")
}
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 db4odb4o 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;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:
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;
}
}
<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:
<listener-class>meuprojeto.dao.Db4oServletContextListener</listener-class>
</listener>
<context-param>Android persistence: db4o
<param-name>db4oFileName</param-name>
<param-value>web.yap</param-value>
</context-param>
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 Upsertwhich serves as both insert and update (store acts as update if the object already exists and that's determined automatically by db4o).
public void savePassword(PassEntry entry) {
if(entry.id == 0)
entry.id = getNextId();
db().store(entry);
db().commit();
}
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!
Opinions expressed by DZone contributors are their own.
Comments