Retrobase — like Retrofit, but for database

Alexander Zhdanov
AndroidPub
Published in
4 min readNov 17, 2016

Many Android developers use Retrofit library, that allows turn HTTP API into java-interface. It’s very convenient, because it reduces the amount of code and this is one of the easiest to use. You just need to create interface and add some annotations.

Recently I was engaged in the development of applications for Android, that was necessary to make queries to the database through the JDBC-driver. Thereat I decided to do something like Retrofit, but for database. So Retrobase appeared, that I’m going to tell you about now.

Эта статья на русском. This article in Russian.

To turn interface and annotation into working code we need Annotation Processing, that gives us great opportunities in generating of similar code. Moreover, in conjunction with JavaPoet we can generate java-code easily and simply.

There are many articles about Annotation Processing in the Internet, so it’s not a problem to understand and deal with it. Plus, all things You need to know to work with JavaPoet is in it’s README.md.

The foundation of Retrobase is two annotaions — DBInterface andDBQuery together withDBAnnotationProcessorthat makes all code generation. WhileDBInterface marks interface with methods-queries to database, DBQuery is used to mark each method. Methods may have parameters, those will be used in SQL-query. For example:

Interface with methods-queries to database

All interesting things happen in DBAnnotationProcessor, where implementation of interface is generated. Generated class have name *interface_name* + Impl.

Creating implementing class that contains methods-wrappers. (Using JavaPoet)

Then the connection to database is created.

Creating field Connection to database (Using JavaPoet)

Also DBAnnotationProcessor creates PreparedStatement for every query.

Creating field PreparedStatement (Using JavaPoet)

… and realization of method for this query.

Creating realization of method-query (Using JavaPoet)

In addition it takes into account return type of method. It can be void if SQL-query is INSERT, DELETE or UPDATE and it doesn’t return anything. Or it can be ResultSet if SQL-query is SELECT.

Farther DBAnnotationProcessor checks whether method can throw Exception. If it can, all exceptions will be throws from realization of method. Else they will be caught and printed to stderr.

All parameters of annotated method are added to implementing method and DBAnnotationProcessor will generate statement that transfers each parameter’s value to PreparedStatement.

Of course, quantity and types of method’s parameters must correlate with parameters of SQL-query that was transmitted by DBQuery annotation.

Generated implementation of method-query. Both method and SQL-query have 3 params of same types.

After generating of interface implementation, DBAnnotationProcessor writes it using instruments of Annotation Processing.

Writing implementing class

Rx it!

Surely, it’s convenient to get ResultSet, while defining only interface, but it can be improved by using popular RxJava. Also using RxJava we can easily make async queries.

To make Retrobase reactive DBMakeRxAnnotationProcessor together with annotations DBInterfaceRx and DBMakeRx were created. You could see example of use of these annotation in example above. Class generated with DBMakeRxAnnotationProcessor will have name *interfave_name* + Rx and will have opened constructor that gets object of interface marked with DBInterfaceRx. Generated class will transfer queries to this object, returning result in reactive style.

Generated class with methods-wrappers. mDB object is realization of interface marked with DBInterfaceRx.

To use DBMakeRxAnnotationProcessor You should add DBMakeRx annotation to method-query. This annotation gets the name of model-class. Generated method-wrapper will return Observable<*model-class*>. If You will not define model-class, generated method will return io.reactivex.Completable that is well for INSERT, DELETE and UPDATE queries those don’t return result.

Generated methods-wrappers for some query-methos from interface above

As you can see, model-class must have opened constructor that gets ResultSet, because generated method-wrapper creates objects of model-class automatically.

Generated method-wrapper will have same quantity and types of parameters as method-query annotated withDBMakeRx.

Params of method-wrapper correlates with params of method-query

All exceptions those will be thrown by method-query will be caught and sent to Observer.

Example of use of generated class with methods-wrappers

Of course, new SpendDBImpl(); or new SpendDBRx(mSpendDB); may be replaced for testing using Dagger or any other DI library.

Source code of Retrobase with comments and working example are open. Feel free to modify them!

The aim of Retrobase is not only make queries to database easier, but show how powerful may be Annotation Processing. There are a lot of code that can be written automatically!

--

--