You’ve been through the Notepad example, and you’ve seen my enhancements in the previous post. But what if you don’t want to have to worry about schema updates and figuring out how to store dates/images/whatever in SQLite? What if you just want to store your objects without all of the SQL-hassle?
Enter Object Oriented Databases.
OODBs are great for small and simple databases, letting you write the fun(ctional) code without the drag of SQL. We’re not talking ORM or Hibernate, with cleverly-hidden SQL. Just simple store(MyObject) and we’re done.
The source code is found in the same place as the DataServices and SQLite project:http://svn.hat6.com/hat6public/DataServiceExample/trunk.
For all of this wonderful functionality, we’ll be using DB4O. They even have a page dedicated to Android, though the source they provide won’t build.
The code matches the DataService interface I provided earlier, so if you’ve read through the SQLite post, you should be able to scan this easily. This file weighs in at just a hair over 200 lines, and a big chunk of that is Exception handling and Comments. So, without further ado, I present to you a working DB4O Android Implementation:
package com.hat6.dataServiceExample.model;
import java.util.List;
import com.db4o.Db4o;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.instrumentation.core.Db4oClassSource;
import android.content.Context;
import android.util.Log;
public class ReminderDB4OImpl implements IReminderDataService {
//TAG for logging.
private static final String TAG = "ReminderDB4OImpl";
/**
* Static variables holding information about our database itself.
* NAME and TABLE are now used to determine where the DB file lives in the filesystem
* VERSION allows our app to know if it has to perform any upgrades
* (Though right now, we aren't using it)
*/
private static final String DATABASE_NAME = "hatsixExamples";
private static final String DATABASE_TABLE = "remindersDS";
private static final int DATABASE_VERSION = 1;
/**
* Context is needed when interacting with the DB
*/
private final Context mCtx;
/**
* The actual Database.
*/
private static ObjectContainer _container = null;
/**
* Constructor - takes the context to allow the database to be
* opened/created
*
* @param ctx the Context within which to work
*/
public ReminderDB4OImpl(Context ctx) {
mCtx = ctx;
open();
Db4oClassSource dbocs; //DB40 crashes if we don't force this to be compiled in.
}
/**
* Open the database. If it cannot be opened, try to create a new
* instance of the database. If it cannot be created, throw an exception to
* signal the failure
*
* @return this (self reference, allowing this to be chained in an
* initialization call)
*/
public IReminderDataService open() {
long startTime = 0;
try {
if(_container == null){
startTime = System.currentTimeMillis();
_container = Db4o.openFile(configure(), db4oDBFullPath());
}
} catch (Exception e) {
Log.e(TAG, e.toString());
return null;
}
Log.d(TAG, "Database opened: " + startTime);
return this;
}
public void close() {
if(_container != null){
long startTime = System.currentTimeMillis();
_container.close();
Log.d(TAG, "Database committed and closed: " + startTime);
_container = null;
}
}
/**
* Create a new reminder record. We pass in a Reminder, rather than just
* some text values like in the Notepad Example. If the reminder is
* successfully created return the new rowId for that reminder, otherwise return
* a -1 to indicate failure.
*
* @param r the Reminder we want saved.
* @return rowId or -1 if failed
*/
public long createReminder(Reminder r) {
if (_container != null){
try {
_container.store(r);
_container.commit();
Log.d(TAG, "Created selected object: " + r.getTitle());
} catch (Exception e){
Log.d(TAG, "Reminder could not be created: " + r.getTitle());
long l = -1;
return l;
}
}
return r.getId();
}
/**
* Fetch all Reminders. This will utilize SQLite's cursors and create an ArrayList
* that is passed back to the caller.
*
* @return List of Reminders
*/
public List<Reminder> fetchAllReminders(){
List<Reminder> list = _container.query(new com.db4o.query.Predicate<Reminder>() {
private static final long serialVersionUID = 1L;
public boolean match(Reminder r) {
return true;
}
});
return list;
}
/**
* Fetch Individual Reminder. We take the 'query by example' approach, where we pass in
* a Reminder. DB40 truly allows us to do query by example, allowing us to search by
* and piece of data. We return the first result, if any.
*
* @param r Reminder with data set to the Reminder we want fetched.
* @return Reminder that we were searching for, or null if not found.
*/
public Reminder fetchReminder(Reminder r){
if (_container != null){
try {
ObjectSet<Reminder> os = _container.queryByExample(r);
return os.get(0);
} catch (Exception e){
Log.d(TAG, "Reminder not found: " + r.getTitle());
return null;
}
}
return null;
}
/**
* Update the reminder using the details provided. We pass in a
* Reminder, create ContentValues from that Reminder, then update
* the record based on rowID
*
* @param r the Reminder to update
* @return true if the reminder was successfully updated, false otherwise
*/
public boolean updateReminder(Reminder r) {
if (_container != null){
try {
_container.store(r);
_container.commit();
Log.d(TAG, "Updated selected object: " + r.getTitle());
} catch (Exception e){
Log.d(TAG, "Reminder not found: " + r.getTitle());
return false;
}
}
return true;
}
/**
* Delete the reminder. Rather than passing in the rowID, we pass the entire
* reminder. Again, by passing objects, our app is significantly simplified.
*
* @param r the Reminder to be deleted.
* @return true if deleted, false otherwise
*/
public boolean deleteReminder(Reminder r) {
if (_container != null){
try {
_container.delete(r);
_container.commit();
Log.d(TAG, "Deleted selected object: " + r.getTitle());
} catch (Exception e){
Log.d(TAG, "Reminder not found: " + r.getTitle());
return false;
}
}
return true;
}
/**
* configure creates a new Configuration and sets some parameters, namely that
* the 'title' should be indexed, and that the DB file should not be locked
* @return Configuration for the OODB
*/
private static com.db4o.config.Configuration configure(){
com.db4o.config.Configuration configuration = Db4o.newConfiguration();
configuration.objectClass(Reminder.class).objectField("title").indexed(true);
configuration.lockDatabaseFile(false);
return configuration;
}
/**
* db4oDBFullPath returns the path to the DB File in the Context's path.
* @return String for the Database.
* @throws Exception
*/
private String db4oDBFullPath() throws Exception {
if (mCtx == null){
throw new Exception("Db4o Module not initialized");
}
return mCtx.getDir(DATABASE_NAME, Context.MODE_PRIVATE) + "/" + DATABASE_TABLE + ".db4o";
}
}