When it comes to persisting data on Android, the officially recommended way is to use the SQLite database. Even though the Android framework provides some kind of support in form of the helper classes, the developer still has to struggle with writing raw SQL queries and manually transforming objects to records in the database. This is where Android ORM database libraries come to give a helping hand.
One way of making data persistence on Android with SQLite database easier is to use a technique called ORM (Object-relational mapping). Several Android ORM database libraries exist, simplifying the database-related boilerplate code to a minimum and abstracting the relational database operations for the developer. Let’s have a look at one of their representatives – GreenDAO
GreenDAO – An Android ORM database
GreenDAO is one of the many existing Android ORM database libraries. To start using it in your project, all you have to do is to define your data model. GreenDAO then generates all the DAOs (Data Access Object) and database helper classes automatically for you.
The main advantage of GreenDAO over other Android ORM database libraries, is its high performance. According to their report for year 2016, GreenDAO outperforms all the other compared ORM libraries. Their benchmarks are open-source and freely available to use and review. Among other features GreenDAO provides, there is session caching, eager loading and database encryption to name a few. The size of the library itself is very small (less than 100 kB) which is also a plus.
Recently, a new version of GreenDAO has been released, bringing new features such as annotations for data model classes or an experimental support for JavaRx. Let’s have a look at how to set the library up in your project.
Setting it up
First of all, I recommend keeping the data model in a separate module from your project’s main module. This is because GreenDAO generates the DAO classes of your data models during the compile time. Therefore, having your data models separate allows you to compile data module first and also the generated code doesn’t interfere with your main project code.
The project top-level build.gradle file should contain a dependency for GreenDAO gradle plugin:
buildscript { repositories { jcenter() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:2.1.2' classpath 'org.greenrobot:greendao-gradle-plugin:3.1.0' } } ...
The datamodel module’s build.gradle file should look like this, containing the GreenDAO plugin specification and library dependency:
apply plugin: 'org.greenrobot.greendao' apply plugin: 'com.android.library' android { ... } greendao { schemaVersion 1 } dependencies { compile 'org.greenrobot:greendao:3.1.0' ... }
GreenDAO allows to configure the gradle plugin by setting the schema version of the database or the directory into which the DAO classes will be generated. More on the gradle plugin configuration can be found in the official documentation.
Schema modelling
Let’s have a look now on how to create a model class to be used with GreenDAO. In our example, we want to store information about people such as their name, their year of birth and a list of their phone numbers. Therefore, we create two data model classes: Person and PhoneNumber. Each class has to be annotated by the @Entity annotation for GreenDAO to recognize it.
@Entity public class Person { @Id private Long id; private String firstName; private String lastName; @Property(nameInDb = "BIRTH_YEAR") @NotNull private int yearOfBirth; @Transient private Integer age; @ToMany(referencedJoinProperty = "contactId") private List<PhoneNumber> numbers; }
@Entity public class PhoneNumber { @Id private Long id; @NotNull @Index(name = "NUMBER_IDX") private String number; @NotNull private Long contactId; }
To explain some of the other annotations used:
- @Id: selects a long/Long property as the entity ID and a primary key
- @Property: allows to redefine the default column name for the property (by default it is transformed e.g. from yearOfBirth to YEAR_OF_BIRTH)
- @NotNull: adds a NOT NULL constrain to the database column (useful for primitive data types)
- @Transient: marks the property to be excluded from the database (e.g. for temporary data)
- @Index: create an index on the property
To define relations between tables/model classes, annotations @ToOne and @ToMany can be used. You just need to specify the joinProperty or referencedJoinProperty respectively.
For a comprehensive list of all options in data modelling, please refer to the official documentation.
DB operations
In order to be able to work with the database objects, the datamodel module has to be compiled first. By doing that, all the DAO classes and helper classes get created. As a convenience, we open a database and create a DAO session upon the application start in the onCreate method of custom Application class so that we can access the DaoSession from anywhere in the code:
public class ExampleApplication extends Application { private DaoSession daoSession; @Override public void onCreate() { super.onCreate(); DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "contacts-db"); Database db = helper.getWritableDb(); daoSession = new DaoMaster(db).newSession(); } public DaoSession getDaoSession() { return daoSession; } }
After that, inserting values into the database is very simple:
// a convenient way of getting the DaoSession object: private DaoSession getAppDaoSession() { return ((ExampleApplication)getApplication()).getDaoSession(); } // insert a new Person with two Phone numbers: Person jack = new Person(null, "Jack", "Brown", 1980); Person james = new Person(null, "James", "Smith", 1977); long jack_id = getAppDaoSession().getPersonDao().insert(jack); long james_id = getAppDaoSession().getPersonDao().insert(james); List<PhoneNumber> phoneNumbers = new ArrayList<>(4); phoneNumbers.add(new PhoneNumber(null, "777443002", jack_id)); phoneNumbers.add(new PhoneNumber(null, "777443003", jack_id)); phoneNumbers.add(new PhoneNumber(null, "676543014", james_id)); phoneNumbers.add(new PhoneNumber(null, "675543103", james_id)); getAppDaoSession().getPhoneNumberDao().insertInTx(phoneNumbers);
We have created two Person objects using the constructors automatically generated by GreenDao, and insert them into the database using appropriate DAO class (PersonDao). Saving the assigned ID numbers, we create a list of PhoneNumber objects and assign them to people using the saved IDs. For demonstration purposes, the phone numbers are inserted into the database in a transaction.
The dump of the database shows what operations have been processed:
sqlite> .dump PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE android_metadata (locale TEXT); INSERT INTO "android_metadata" VALUES('en_US'); CREATE TABLE "PHONE_NUMBER" ("_id" INTEGER PRIMARY KEY ,"NUMBER" TEXT NOT NULL ,"CONTACT_ID" INTEGER NOT NULL ); INSERT INTO "PHONE_NUMBER" VALUES(1,'777443002',1); INSERT INTO "PHONE_NUMBER" VALUES(2,'777443003',1); INSERT INTO "PHONE_NUMBER" VALUES(3,'676543014',2); INSERT INTO "PHONE_NUMBER" VALUES(4,'675543103',2); CREATE TABLE "PERSON" ("_id" INTEGER PRIMARY KEY ,"FIRST_NAME" TEXT,"LAST_NAME" TEXT,"BIRTH_YEAR" INTEGER NOT NULL ); INSERT INTO "PERSON" VALUES(1,'Jack','Brown',1980); INSERT INTO "PERSON" VALUES(2,'James','Smith',1977); CREATE INDEX NUMBER_IDX ON PHONE_NUMBER ("NUMBER" ASC); COMMIT;
Querying the database is made simple with the QueryBuilder. An example shows getting all objects of a certain type from the database and creating a query using join:
// get all Person objects in the DB and their Phone numbers List<Person> persons = getAppDaoSession().getPersonDao().loadAll(); for (Person p : persons) { System.out.println(p); } // join example: get only Person objects with PhoneNumbers starting with "7" QueryBuilder<Person> queryBuilder = getAppDaoSession().getPersonDao().queryBuilder(); queryBuilder.join(PhoneNumber.class, PhoneNumberDao.Properties.ContactId) .where(PhoneNumberDao.Properties.Number.like("7%")); List<Person> joined = queryBuilder.list(); for (Person p : joined) { System.out.println(p); }
A few more examples of database operations made easy with GreenDAO follow:
// update and delete Person Person james = getAppDaoSession().getPersonDao() .queryBuilder().where(PersonDao.Properties.FirstName.eq("James")).unique(); Person jack = getAppDaoSession().getPersonDao() .queryBuilder().where(PersonDao.Properties.FirstName.eq("Jack")).unique(); james.setLastName("Smooth"); james.update(); getAppDaoSession().getPhoneNumberDao().deleteInTx(jack.getNumbers()); jack.delete();
After the operations are executed we can confirm that the information about the person “Jack” have been removed together with phone numbers associated with him. Also, the surname of “James” has been successfully updated:
sqlite> .dump PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE android_metadata (locale TEXT); INSERT INTO "android_metadata" VALUES('en_US'); CREATE TABLE "PHONE_NUMBER" ("_id" INTEGER PRIMARY KEY ,"NUMBER" TEXT NOT NULL ,"CONTACT_ID" INTEGER NOT NULL ); INSERT INTO "PHONE_NUMBER" VALUES(3,'676543014',2); INSERT INTO "PHONE_NUMBER" VALUES(4,'675543103',2); CREATE TABLE "PERSON" ("_id" INTEGER PRIMARY KEY ,"FIRST_NAME" TEXT,"LAST_NAME" TEXT,"BIRTH_YEAR" INTEGER NOT NULL ); INSERT INTO "PERSON" VALUES(2,'James','Smooth',1977); CREATE INDEX NUMBER_IDX ON PHONE_NUMBER ("NUMBER" ASC); COMMIT;
Conclusion
GreenDAO is one of the many Android ORM database libraries available. What makes it stand out is its performance and advanced caching support. It’s being actively developed and new features are added with each new release.
In this post, it has been described how to set up a project with GreenDAO, how to create data model classes and several common database operations have been presented.