Analyse: compare 3 frameworks to access SQLite database under Android
From my experience I can say there are three main frameworks to work locally with SQLite database under Android mobile system: native SQLite android library, ORMlite framework and sqlitegen framework.
It will be analysed 3 valuable function’s parts to choice an optimal framework, what suits for the most android projects, as follows: performance, functionality and stability.
Introduction
Native Android SQLite library
Exposes methods to manage a SQLite database. SQLiteDatabase has all methods to create, delete, execute SQL commands, and perform other common database management tasks. More info is here
ORMlite
ORMLite is an open source software framework that provides lightweight object relational mapping (ORM) between Java classes and SQL database. It supports Android mobile platform. The framework provides a simple powerful wrapper around the DAO functions without the complexity of other frameworks such as Hibernate.
Example:
@DatabaseTable
public class TestGroup implements Serializable {
@DatabaseField(generatedId = true)
private Integer id;
@DatabaseField
private String name;
public Integer getId() {return id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
@DatabaseTable
public class TestDetail implements Serializable {
public static final String DATE_FIELD_NAME = “lastClickDate”;
@DatabaseField(generatedId = true)
private Integer id;
@DatabaseField(columnName = DATE_FIELD_NAME)
private Date lastClickDate;
@DatabaseField(index = true)
private String name;
@DatabaseField(canBeNull = true, foreign = true)
private TestGroup group;
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public TestGroup getGroup() {return group;}
public void setGroup(TestGroup group) {this.group = group;}
public Date getLastClickDate() {return lastClickDate; }
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
As we can see in example above, the framework supports primary and foreign keys, indexes and relationships between tables. So we have here everything to create data model as well as re-engeeniring it within our android project. More info is here
Sqlitegen
Actually it is a plugin to Eclipse, what generates the classes, having all the tedious code for persisting the object described by the interface. It will be generated by the plugin at project build time from an annotated interface that described the table and fields for the class. The generated class will be designed to be efficient at runtime; it won’t need to use reflection etc.
package android.androidVNC;
import com.antlersoft.android.db.*;
@TableInterface(ImplementingClassName=“AbstractConnectionBean”,TableName=“CONNECTION_BEAN”)
interface IConnectionBean{
@FieldAccessor
long get_Id();
@FieldAccessor
String getNickname();
@FieldAccessor
String getAddress();
@FieldAccessor
int getPort();
@FieldAccessor
String getPassword();
@FieldAccessor
String getColorModel();
@FieldAccessor
boolean getForceFull();
@FieldAccessor
String getRepeaterId();
}
// Do not edit this file directly
package android.androidVNC;
publicabstractclassAbstractConnectionBeanextends com.antlersoft.android.dbimpl.IdImplementationBase
implements IConnectionBean{
public staticfinalString GEN_TABLE_NAME =“CONNECTION_BEAN”;
public staticfinalint GEN_COUNT =8;
// Field constants
public staticfinalString GEN_FIELD__ID =“_ID”;
public staticfinalint GEN_ID__ID =0;
public staticfinalString GEN_FIELD_NICKNAME =“NICKNAME”;
public staticfinalint GEN_ID_NICKNAME =1;
public staticfinalString GEN_FIELD_ADDRESS =“ADDRESS”;
publicstaticfinalint GEN_ID_ADDRESS =2;
publicstaticfinalString GEN_FIELD_PORT =“PORT”;
publicstaticfinalint GEN_ID_PORT =3;
publicstaticfinalString GEN_FIELD_PASSWORD =“PASSWORD”;
publicstaticfinalint GEN_ID_PASSWORD =4;
publicstaticfinalString GEN_FIELD_COLORMODEL =“COLORMODEL”;
publicstaticfinalint GEN_ID_COLORMODEL =5;
publicstaticfinalString GEN_FIELD_FORCEFULL =“FORCEFULL”;
publicstaticfinalint GEN_ID_FORCEFULL =6;
publicstaticfinalString GEN_FIELD_REPEATERID =“REPEATERID”;
publicstaticfinalint GEN_ID_REPEATERID =7;
// SQL Command for creating the table
publicstaticString GEN_CREATE =“CREATE TABLE CONNECTION_BEAN (“+
“_ID INTEGER PRIMARY KEY AUTOINCREMENT,”+
“NICKNAME TEXT,”+
“ADDRESS TEXT,”+
“PORT INTEGER,”+
“PASSWORD TEXT,”+
“COLORMODEL TEXT,”+
“FORCEFULL INTEGER,”+
“REPEATERID TEXT”+
“)”;
// Members corresponding to defined fields
privatelong gen__Id;
private java.lang.String gen_nickname;
private java.lang.String gen_address;
privateint gen_port;
private java.lang.String gen_password;
private java.lang.String gen_colorModel;
privateboolean gen_forceFull;
private java.lang.String gen_repeaterId;
publicStringGen_tableName(){return GEN_TABLE_NAME;}
// Field accessors
publiclong get_Id(){return gen__Id;}
publicvoid set_Id(long arg__Id){ gen__Id = arg__Id;}
public java.lang.String getNickname(){return gen_nickname;}
publicvoid setNickname(java.lang.String arg_nickname){ gen_nickname = arg_nickname;}
public java.lang.String getAddress(){return gen_address;}
publicvoid setAddress(java.lang.String arg_address){ gen_address = arg_address;}
publicint getPort(){return gen_port;}
publicvoid setPort(int arg_port){ gen_port = arg_port;}
public java.lang.String getPassword(){return gen_password;}
publicvoid setPassword(java.lang.String arg_password){ gen_password = arg_password;}
public java.lang.String getColorModel(){return gen_colorModel;}
publicvoid setColorModel(java.lang.String arg_colorModel){ gen_colorModel = arg_colorModel;}
publicboolean getForceFull(){return gen_forceFull;}
publicvoid setForceFull(boolean arg_forceFull){ gen_forceFull = arg_forceFull;}
public java.lang.String getRepeaterId(){return gen_repeaterId;}
publicvoid setRepeaterId(java.lang.String arg_repeaterId){ gen_repeaterId = arg_repeaterId;}
public android.content.ContentValuesGen_getValues(){
android.content.ContentValues values=new android.content.ContentValues();
values.put(GEN_FIELD__ID,Long.toString(this.gen__Id)); evhputgf bhgifu fghk values.put(GEN_FIELD_NICKNAME,this.gen_nickname);
values.put(GEN_FIELD_ADDRESS,this.gen_address);
values.put(GEN_FIELD_PORT,Integer.toString(this.gen_port));
values.put(GEN_FIELD_PASSWORD,this.gen_password);
values.put(GEN_FIELD_COLORMODEL,this.gen_colorModel);
values.put(GEN_FIELD_FORCEFULL,(this.gen_forceFull ?“1”:“0”));
values.put(GEN_FIELD_REPEATERID,this.gen_repeaterId);
return values;
} /**
* Return an array that gives the column index in the cursor for each field defined
* @param cursor Database cursor over some columns, possibly including this table
* @return array of column indices; -1 if the column with that id is not in cursor
*/
publicint[]Gen_columnIndices(android.database.Cursor cursor){
int[] result=newint[GEN_COUNT];
result[0]= cursor.getColumnIndex(GEN_FIELD__ID);
result[1]= cursor.getColumnIndex(GEN_FIELD_NICKNAME);
result[2]= cursor.getColumnIndex(GEN_FIELD_ADDRESS);
result[3]= cursor.getColumnIndex(GEN_FIELD_PORT);
result[4]= cursor.getColumnIndex(GEN_FIELD_PASSWORD);
result[5]= cursor.getColumnIndex(GEN_FIELD_COLORMODEL);
result[6]= cursor.getColumnIndex(GEN_FIELD_FORCEFULL);
result[7]= cursor.getColumnIndex(GEN_FIELD_REPEATERID);
return result;
}
/**
* Populate one instance from a cursor
*/
publicvoidGen_populate(android.database.Cursor cursor,int[] columnIndices){
if( columnIndices[GEN_ID__ID]>=0&&! cursor.isNull(columnIndices[GEN_ID__ID])){
gen__Id = cursor.getLong(columnIndices[GEN_ID__ID]);
}
if( columnIndices[GEN_ID_NICKNAME]>=0&&! cursor.isNull(columnIndices[GEN_ID_NICKNAME])){
gen_nickname = cursor.getString(columnIndices[GEN_ID_NICKNAME]);
}
if( columnIndices[GEN_ID_ADDRESS]>=0&&! cursor.isNull(columnIndices[GEN_ID_ADDRESS])){
gen_address = cursor.getString(columnIndices[GEN_ID_ADDRESS]);
}
if( columnIndices[GEN_ID_PORT]>=0&&! cursor.isNull(columnIndices[GEN_ID_PORT])){
gen_port =(int)cursor.getInt(columnIndices[GEN_ID_PORT]);
}
if( columnIndices[GEN_ID_PASSWORD]>=0&&! cursor.isNull(columnIndices[GEN_ID_PASSWORD])){
gen_password = cursor.getString(columnIndices[GEN_ID_PASSWORD]);
}
if( columnIndices[GEN_ID_COLORMODEL]>=0&&! cursor.isNull(columnIndices[GEN_ID_COLORMODEL])){
gen_colorModel = cursor.getString(columnIndices[GEN_ID_COLORMODEL]);
}
if( columnIndices[GEN_ID_FORCEFULL]>=0&&! cursor.isNull(columnIndices[GEN_ID_FORCEFULL])){
gen_forceFull =(cursor.getInt(columnIndices[GEN_ID_FORCEFULL])!=0);
}
if( columnIndices[GEN_ID_REPEATERID]>=0&&! cursor.isNull(columnIndices[GEN_ID_REPEATERID])){
gen_repeaterId = cursor.getString(columnIndices[GEN_ID_REPEATERID]);
}
}
/**
* Populate one instance from a ContentValues
*/
publicvoidGen_populate(android.content.ContentValues values){
gen__Id = values.getAsLong(GEN_FIELD__ID);
gen_nickname = values.getAsString(GEN_FIELD_NICKNAME);
gen_address = values.getAsString(GEN_FIELD_ADDRESS);
gen_port =(int)values.getAsInteger(GEN_FIELD_PORT); gen_password = values.getAsString(GEN_FIELD_PASSWORD);
gen_colorModel = values.getAsString(GEN_FIELD_COLORMODEL);
gen_forceFull =(values.getAsInteger(GEN_FIELD_FORCEFULL)!=0);
gen_repeaterId = values.getAsString(GEN_FIELD_REPEATERID);
}
}
My opinion is, the framework provides no mapping here, it generates DAO classes, what should not be changed. The code generation is not optimal and not flexible. And the effort, what an android developer needs to implement and re-design a data structure, using the framework, is higher than in Native SQLite. I think the framework is not comfortable for db development and I cannot recommend it for usage in db design. So I will not analyse this in my following test any more.
Performance test: ORMlite vs. Native SQLite
To compare the performance of ORMlite and Native SQL frameworks I have created two identical tables with 2 fields: auto incremented primary key id and one long value. The test program inserts 1000 rows as long values and shows processing time for both frameworks:
I have repeated the test 10 times. Here is the result:
Vertical axis is execution time in miliseconds, horizontal one is iterations.
As we can see on the diagram above, the Native Android SQLite library running approximately 20% faster than ORMlite framework.
Stability test of ORMlite
Stability of native SQLite android library stays not under question, so I will skip the investigation of this framework and go to analyse the ORMlite.
The stability test of ORMlite framework creates two tables: master and detail (see source example above) and makes updates of detail table:
After more as 220000 updates I have received no errors or exceptions with the framework. I can suppose the ORMlite is running stable with implemented functionality.
Frankly speaking the total test of stability needs more complicated examples to check more complex data structures and database functionality. But this takes to much time, what I cannot invest into analyse now. So I follow the feedback of other users from internet community, who has the ORMlite in production already and has positive opinion about the stability of the framework.
My experience says, there are a lot of situation during db structure design, where a developer makes a relationship error and than attempts to execute his project. The result is the app hangs and the framework outputs no error log. It is a bad place in the framework within development time. To try to avoid the problem I can recommend to compile and execute your project after so oft as you create new table or new relationship.
Conclusion
Comparing table for ORMlite and Native SQLite library:
ORMlite |
Native SQLite |
|
Mapping of useful DB functions (definition of field, primary and foreign keys, index, relationship) what can increase development speed |
+ |
– |
Advanced DAO functions (transaction, etc.) |
– |
+ |
Possibility for comfortable re-engineering of database structure |
+ |
– |
Performance |
– |
+ |