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

Real Android apps leveraging db4o persistence engine (Part 1)

07.19.2010
| 9457 views |
  • submit to reddit
This the first delivery in a series of articles targeted at showing developers how db4o (an open source database that leverages today's object-oriented languages, systems, and mindset) is being used in several Android projects to avoid all the pitfalls and hassles of object-relational mapping while benefiting from an elegant and straight forward way to evolve a domain model which, in the end, translates into faster, easier upgrades for users.

There are many benefits to using an object database like db4o, including easier code maintenance, and the ability to create applications based on more complex data models. Unlike in rigid, predefined SQL tables, you can store dynamic, free-form data, which can be changed or amended any time. In addition, db4o allows for data replication, another missing element in Android's software stack.

Let's take a look at the code in these projects to learn how developers leverage object database technology on their apps and also use the opportunity to introduce key concepts about db4o. Let's start with project DyCaPo.

DyCaPo stands for “Dynamic Car Pooling”, a system that facilitates the ability of drivers and passengers to make one-time ride matches close to their departure time, with sufficient convenience and flexibility to be used on a daily basis. The project is the result of research activities on the adoption of a FREE/OPEN Dynamic Carpooling system in the province of Trento, Italy.

Riccardo Buttarelli, chose db4o as the persistence engine in the client application for the DyCaPo Service running on Android OS (aka dycadroid). If you check dycadroid's db4o configuration:

private static Configuration configure(){

	dbConfiguration = Db4o.newConfiguration();

	dbConfiguration.objectClass(Trip.class).objectField(Trip.ID).indexed(true);
	dbConfiguration.objectClass(Trip.class).cascadeOnUpdate(true);
	dbConfiguration.objectClass(Trip.class).cascadeOnDelete(true);
	dbConfiguration.objectClass(Location.class).objectField(Location.GEORSSPNT).indexed(true);
	dbConfiguration.objectClass(Location.class).cascadeOnDelete(true);
	dbConfiguration.objectClass(Location.class).cascadeOnUpdate(true);
	dbConfiguration.objectClass(Person.class).objectField(Person.USERNAME).indexed(true);
	dbConfiguration.objectClass(Person.class).cascadeOnUpdate(true);
	dbConfiguration.objectClass(Person.class).cascadeOnDelete(true);
	//[...]
	dbConfiguration.objectClass(ActiveTrip.class).objectField(ActiveTrip.ID).indexed(true);
	dbConfiguration.objectClass(ActiveTrip.class).cascadeOnUpdate(true);
	dbConfiguration.objectClass(ActiveTrip.class).cascadeOnDelete(true);
	dbConfiguration.objectClass(Route.class).cascadeOnUpdate(true);
	dbConfiguration.objectClass(Route.class).cascadeOnDelete(true);
	dbConfiguration.lockDatabaseFile(false);
	dbConfiguration.messageLevel(2);

	return dbConfiguration;
}
you'll see that he does a heavy use of cascading on updates and deletes in the db4o configuration for several classes. When you use cascading in db4o you can save a lot of time because you're basically telling the database to apply an operation on your object considering all the objects that are referenced from it (object tree). For example, when you delete an ActiveTrip object you'll probably want to also delete all the Route objects that are referenced from it. In order to achieve this functionality automatically you’ll just have to enable cascaded deletes for that class:
dbConfiguration.objectClass(ActiveTrip.class).cascadeOnDelete(true);
and forget about managing deletion of referenced objects!

Cascaded deletes can be applied to all members of an object or can be limited to specific fields.

On the other hand, if you changed an ActiveTrip object tree you also probably want to reflect the changes on the associated Routes automatically (that are referenced from the ActiveTrip object). It's quite easy to achieve that by enabling cascaded updates on the class
dbConfiguration.objectClass(ActiveTrip.class).cascadeOnUpdate(true);
this way changed Route objects will also be updated on the database when you update an ActiveTrip and you won't have to traverse all the referenced objects to look for updated data (let db4o handle that!)

As you might have guessed already the trade-off of a cascading-heavy configuration is database performance: if you're dealing with deep object structures you might want to exercise a more granular control during updates (ie. manually set the update depth). By default db4o uses an update depth of 1 meaning that only primitives values in the object will be updated (not the changes in referenced objects). You can set the update depth to a fixed value of your choice so stores() can go as deep as you want or you can just use cascaded updates all the object tree will be traversed looking for changes.

If you want full automation on database operation you can try the Transparent Persistance framework. Basically this is a way to make your object fully database-aware. You store the object once in the database. After that, all changes you make on the object are reflected in the database ‘by magic’. You can implement this manually or use byte-code-enhancers.

If you take a closer look at the configuration code above you’ll also notice that some fields in specific classes are configured as “indexed”:
dbConfiguration.objectClass(ActiveTrip.class).objectField(ActiveTrip.ID).indexed(true);
Indexing works like in the relational world and will give you a faster access when you're querying over that field. db4o automatically uses indexes for queries if they are present. Lots of complaints about slow queries have to do with the omission of indexes in the configuration.

We saw some tips related to updates and deletes but what about fetching objects? The most important concept of object retrieval is the concept of “activation” and has many similarities with updates and deletes in terms of cascading. Suppose I want to load an ActiveTrip from the database, shall I also get all the associated Routes in the same operation? What if I just need to know the value of an int field in the ActiveTrip but Routes are irrelevant to me for this operation?
db4o allows you to handle how deep in the object tree you want to go when loading an object (aka “activation depth”). Same as before, via the configuration you can opt to activate everything when you fetch the parent object via cascading:
dbConfiguration.objectClass(ActiveTrip.class).cascadeOnActivate(true);
or if you want more control you can go with a manual activation (ObjectContainer#activate(object, depth);) or let db4o handle everything via the transparent activation framework which activates object trees from the database on demand (ie. as object members are accessed).

Right in the end of the configuration code you’ll find the line:
dbConfiguration.lockDatabaseFile(false);
which is listed as our number one dangerous practice! During normal operation, and if you don't use the configuration option above, db4o locks the database file to prevent concurrent access which could leave you with a corrupted database. If you find yourself struggling with constant DatabaseFileLockedExceptions that's a clear sign that you need to change your database access pattern: if you need multiple concurrent transactions you can try db4o’s embedded client server mode of operation.

So, we've seen the db4o configuration options in dycadroid but how difficult are the standard upsert, delete, query operations? Actually extremely easy!

The beauty of db4o is that you don't have to do any kind of processing to your object in order to make it persistent (ie. no mapping). You just pass the full object as a parameter to the store() method of a db4o’s object container and voila it's saved (and also the sibling objects depending on your configuration). The store() operation acts as an insert if the passed object is new (ie. was never stored in the db during the current transaction) or as an update if the object was previously saved during the same session (hence we could call it an ”upsert” operation):
public static void saveActiveTrip (ActiveTrip trip){
	Log.d(TAG, "saving ActiveTrip from trip");
        ObjectContainer db = DBProvider.getDatabase();
        //[...]
        db.store(trip);
        db.commit();
}
Note the commit() in the last line above. As any decent transactional database db4o supports commit and rollback operations during transactions. Transaction semantics is implicit to match db4o's simplicity. In the code above a transaction is started when the object container is opened (eg. openFile() inside getDatabase()) and ends when the object container is closed (db.close()). Along the same lines of simplicity a close() operation will force a commit().

 

Important: after a close() operation in an object container all the objects that where saved to db4o will be seen as new objects when you open a new object container (for more information see db4o's unique identity concept).

 Similarly when you want to delete an object from the database you also interact with a db4o object container but this time it's delete() that you must use. Same rules apply with regards to object identity: in order for delete to work you must have stored or fetched the object passed as parameter during the same transaction otherwise db4o won’t be able to tell that the object belongs to the database and the operation will be ineffective. The delete operation applies to the passed object only and does not delete the siblings except if you configured cascaded deletes (see above).

How about querying? Querying with db4o can be as easy as issuing one line of code. You have the choice of 3 different query mechanisms:

  1. Native Queries: refactorable query interface that is very close to the language. The queries are optimized and converted to SODA queries behind the curtains. In db4o for .NET a LINQ provider is also provided and is the preferred method for querying. Since Dalvik (Android VM) uses it’s own binary format db4o’s native query optimizer won’t work during runtime. If you want to optimize NQs on Android you’ll have to use db4o’s build time native query enhancer.
  2. SODA Queries: low level and powerful graph based query system where you basically build a query tree and pass it to db4o for execution.
  3. Query by Example: useful for simple queries where you pass a prototype object that will serve as an example to find similar objects. Uses reflection to analyze the passed prototype object and build the query.

Here's an example (also from dycadroid) that shows a delete plus a simple query using “query by example”:
public static boolean deletePrefs(){
	try{
		ObjectContainer db = DBProvider.getDatabase();
		ObjectSet prefs = db.queryByExample(new Preferences());
		db.delete(prefs);
		db.commit();
		return true;
        }catch (Exception e){
		return false;
        }
}
Note that in the example query by example is used to retrieve all instances of class Preferences:
db.queryByExample(new Preferences());
but you could also pass a prototypical instance with some fields filled in to further constraint the query:
db.queryByExample(new Preferences("trip", null, null));
which will retrieve all objects of class Preferences where the type is ”trip” and won't pay attention to the remaining values in the constructor (will match anything there).

Well, I hope you have enjoyed db4o's simplicity by taking a look at parts of a real project. On the next article in the series we'll take a look at QuiteSleep another Android app that is available in the Android Market that leverages db4o for all persistence needs.
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

Cesar Valiente replied on Thu, 2010/08/12 - 2:58am

Great article Germán!!!!

db4o is an excellent option for Android apps that not need use the complexity of E-R ddbb, with a few code lines you can create a complete data model for your app, using queries, inserts, deletes, updates, configurations...  and more important, your classes model in your app is your data model in your object database!!

 Greetings.

Krishna Sharma replied on Sun, 2014/02/16 - 1:55am

 Hi German , I loved your tutorial, I am looking forward to switch from SQLite to DB40 for My Android Projects, I have a Question , how dose DB4o avoids the data redundancy.

for Example I have two class

Class School {

school info 1....

school info 2....

school info n....

}

Class Student {

School mSchool;

Student info 1..

Student info 2..

Student info n..

School newEra=new School("New Era");

Student S1=new Student();

Student S2=new Student();

Student S3=new Student();

Student S4=new Student();

S1.mSchool=newEra;

S2.mSchool=newEra;

S3.mSchool=newEra;

S4.mSchool=newEra;

If we save Object S1,S2,S3,S4 in DB4o ,

Q1 . since School is a field in Student Class will it be saved multiple time along with S1,S2,S3,S4. 

Q2. if Q1 ans is No then what happens when we delete  newEra from DB , will it be removed from S1,S2,S3,S4 also?

and Thanks for the Tutorial.

Comment viewing options

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