Object Relational Mapping Lite (ORM Lite) provides some simple, lightweight functionality for persisting Java objects to SQL databases while avoiding the complexity and overhead of more standard ORM packages. Old and proven ORM. Somewhat outdated.
dependencies {
implementation 'com.j256.ormlite:ormlite-android:5.1'
}
ObjectBox < Realm < GreenDAO < Room < SQLite < ORMLite.
Very questionable, depends on complexity of queries, read/write preferences, app architecture etc.
Realm and ObjectBox are very fast, comparable to clean SQLite or even faster, but have some downsides.
If you have worked with SQLite on Android you have worked with the SQLiteOpenHelper class. When working with ORMLite you need to extend the OrmLiteSqliteOpenHelper instead, but they are similar in the way that they have an onCreate, an onUpgrade ect. And you do the same kind of operations in them but with the help of ORMLite.
@DatabaseTable(tableName = "accounts")
public class Account {
@DatabaseField(columnName = "id", generatedId = true)
private String name;
@DatabaseField(columnName = "password")
private String password;
public Account() {
// ORMLite needs a no-arg constructor
}
/** Getters & Setters **/
}
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String DATABASE_NAME = "ormlite.db";
private static final int DATABASE_VERSION = 1;
private Dao<Account, Integer> accountDao = null;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
try {
TableUtils.createTable(connectionSource, Account.class);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource,
int oldVersion, int newVersion) {
try {
TableUtils.dropTable(connectionSource, Account.class, true);
onCreate(db, connectionSource);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public Dao<Account, Integer> getAccountDao() throws SQLException {
if (accountDao == null) {
accountDao = getDao(Account.class);
}
return accountDao;
}
@Override
public void close() {
accountDao = null;
super.close();
}
}
DatabaseHelper helper = new DatabaseHelper(this);
Dao<Account, Integer> accountDao = helper.getAccountDao();
Account account = new Account("name", "password");
accountDao.create(account);
-
create(Collection datas)
-
create(T data)
-
createOrUpdate(T data)
-
queryForAll()
-
queryForId(ID id)
-
update(T data)
-
delete(T data)
-
deleteById(ID id)
-
executeRaw(String statement, String... arguments) - Run a raw execute SQL statement to the database.
-
refresh(T data) - Does a query for the data's id and copies values from the database to refresh the data parameter.
Extend BaseDaoImpl.
public class CustomDao<T, ID> extends BaseDaoImpl<T, ID> {
protected CustomDao(final Class<T> dataClass) throws SQLException {
super(dataClass);
}
@Override
public int create(final T data) throws SQLException {
int result = super.create(data);
// Send an event with EventBus or Otto
return result;
}
}
@DatabaseTable(tableName = "accounts", daoClass = CustomDao.class)
public class Account {
...
}
GreenDAO is a light & fast ORM solution for Android that maps objects to SQLite databases.
buildscript {
repositories {
mavenCentral() // add repository
}
dependencies {
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin
}
}
apply plugin: 'org.greenrobot.greendao' // apply plugin
dependencies {
implementation 'org.greenrobot:greendao:3.2.2' // add library
}
-
Maximum performance (probably the fastest ORM for Android, was the fastest).
-
Easy to use powerful APIs covering relations and joins.
-
Minimal memory consumption.
-
Small library size (<100KB) to keep build times low and to avoid the 65k method limit.
-
Database encryption: greenDAO supports SQLCipher to keep user’s data safe.
-
Strong community: More than 10.000 GitHub stars show there is a strong and active community.
ObjectBox < Realm < GreenDAO < Room < SQLite < ORMLite.
Very questionable, depends on complexity of queries, read/write preferences, app architecture etc.
Realm and ObjectBox are very fast, comparable to clean SQLite or even faster, but have some downsides.
-
DaoMaster
The entry point for using greenDAO. Holds the database object (SQLiteDatabase) and manages DAO classes (not objects) for a specific schema. It has static methods to create the tables or drop them. Its inner classes OpenHelper and DevOpenHelper are SQLiteOpenHelper implementations that create the schema in the SQLite database.
-
DaoSession
Manages all available DAO objects for a specific schema, which you can acquire using one of the getter methods. Provides also some generic persistence methods like insert, load, update, refresh and delete for entities. Keeps track of an identity scope.
-
DAOs
Persists and queries for entities. For each entity, greenDAO generates a DAO. It has more persistence methods than DaoSession, for example: count, loadAll, and insertInTx.
-
Entities
Persistable objects. Usually, entities are objects representing a database row using standard Java properties (like a POJO).
@Entity(nameInDb = "note")
public class Note {
@Id(autoincrement = true)
private Long id;
@Property(nameInDb = "text")
private String text;
}
Initialization in App class. DevOpenHelper is for dev only. DevOpenHelper will drop all tables on schema changes (in onUpgrade()). So it is recommended to create and use a subclass of DaoMaster.OpenHelper instead. DAO classes generated after build.
public class App extends Application {
private DaoSession daoSession;
@Override
public void onCreate() {
super.onCreate();
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db");
Database db = helper.getWritableDb();
// Database db = helper.getEncryptedWritableDb("encryption-key");
daoSession = new DaoMaster(db).newSession();
}
public DaoSession getDaoSession() {
return daoSession;
}
}
Note note = new Note();
DaoSession daoSession = ((App) getApplication()).getDaoSession();
noteDao = daoSession.getNoteDao();
noteDao.insert(note);
noteDao.deleteByKey(id);
note.changeNote();
noteDao.update(note);
List<Note> notes = noteDao.queryBuilder()
.where(Properties.PropertyName.eq("some property value"))
.orderAsc(Properties.OtherPropertyName)
.list();
Example mapping an enum to an Integer.
@Entity
public class User {
@Id
private Long id;
@Convert(converter = RoleConverter.class, columnType = Integer.class)
private Role role;
public enum Role {
DEFAULT(0), AUTHOR(1), ADMIN(2);
final int id;
Role(int id) {
this.id = id;
}
}
public static class RoleConverter implements PropertyConverter<Role, Integer> {
@Override
public Role convertToEntityProperty(Integer databaseValue) {
if (databaseValue == null) {
return null;
}
for (Role role : Role.values()) {
if (role.id == databaseValue) {
return role;
}
}
return Role.DEFAULT;
}
@Override
public Integer convertToDatabaseValue(Role entityProperty) {
return entityProperty == null ? null : entityProperty.id;
}
}
}
- Put greendao gradle plugin before kotlin plugin when applying plugins in app gradle file.
apply plugin: 'org.greenrobot.greendao'
apply plugin: 'kotlin-android'
- If you configure greenDao in gradle (e.g. set schema or generate location), then also put this sript into app gradle file, which allows greendao gradle task to run before kotlin debug and generate necessary classes.
tasks.whenTaskAdded { task ->
if (task.name == 'kaptDebugKotlin') {
task.dependsOn tasks.getByName('greendao')
}
}
- GreenDAO entities should be written in java.
ObjectBox is a super fast object-oriented NoSQL database, built uniquely for IoT and Mobile devices. From developers of GreenDAO.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "io.objectbox:objectbox-gradle-plugin:2.3.4"
}
}
apply plugin: 'io.objectbox' // apply last
ObjectBox < Realm < GreenDAO < Room < SQLite < ORMLite.
Very questionable, depends on complexity of queries, read/write preferences, app architecture etc.
Realm and ObjectBox are very fast, comparable to clean SQLite or even faster, but have some downsides.
Entities must have one @Id property of type long. By default IDs for new objects are assigned by ObjectBox.
@Entity
public class User {
@Id public long id;
@Index
@NameInDb("username")
public String name;
@Transient
private int tempUsageCount; // not persisted
// getters and setters ...
}
-
MyObjectBox: Generated based on your entity classes, MyObjectBox supplies a builder to set up a BoxStore for your app.
-
BoxStore: The entry point for using ObjectBox. BoxStore is your direct interface to the database and manages Boxes.
-
Box: A box persists and queries for entities. For each entity, there is a Box (supplied by BoxStore).
Probaby in application subclass.
BoxStore boxStore = MyObjectBox.builder()
.androidContext(context.getApplicationContext())
.build();
The Box class is likely the class you interact with most. You get Box instances via BoxStore.boxFor(). A Box instance gives you access to objects of a particular type.
Box<User> userBox = boxStore.boxFor(User.class);
User user = new User();
userBox.put(user);
user.setName("some name");
userBox.put(user);
List<User> users = userBox.query().equal(User_.name, "some name").build().find();
// Explicit transaction.
boxStore.runInTx(() -> {
for(User user: allUsers) {
modify(user);
box.put(user);
}
});
userBox.remove(user);
-
put: Inserts a new or updates an existing object with the same ID. When inserting and put returns, an ID will be assigned to the just inserted object. put also supports putting multiple objects, which is more efficient. Runs an implicit transaction.
-
get and getAll: Given an object’s ID reads it back from its box.
-
remove and removeAll: Remove a previously put object from its box (deletes it). remove also supports removing multiple objects, which is more efficient.
-
count: Returns the number of objects stored in this box.
-
query: Starts building a query to return objects from the box that match certain conditions.
The QueryBuilder class lets you build custom queries for your entities. Create an instance via Box.query(). QueryBuilder offers several methods to define query conditions for properties of an entity. To specify a property ObjectBox does not use Strings but meta information "underscore" classes (like User_) that are generated during build time. The meta information classes have a static field for each property (like User_.name). This allows to reference properties safely with compile time checks to prevent runtime errors, for example because of typos.
-
equal(), notEqual(), greater() and less().
-
isNull() and notNull().
-
between() to filter for values that are between the given two.
-
in() and notIn() to filter for values that match any in the given array.
-
startsWith(), endsWith() and contains() for extended String filtering.
-
and() and or() to build more complex combinations of conditions.
-
order() to order the returned results.
-
find(), findFirst(), findUnique() to retrieve objects matching the query.
Realm is a mobile NoSQL database, a replacement for SQLite & ORMs.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:5.11.0"
}
}
apply plugin: 'realm-android'
-
Mobile-first: Realm is the first database built from the ground up to run directly inside phones, tablets, and wearables.
-
Simple: Data is directly exposed as objects and queryable by code, removing the need for ORM's riddled with performance & maintenance issues. Plus, we've worked hard to keep our API down to very few classes: most of our users pick it up intuitively, getting simple apps up & running in minutes.
-
Modern: Realm supports easy thread-safety, relationships & encryption.
-
Fast: Realm is faster than even raw SQLite on common operations while maintaining an extremely rich feature set.
ObjectBox < Realm < GreenDAO < Room < SQLite < ORMLite.
Very questionable, depends on complexity of queries, read/write preferences, app architecture etc.
Realm and ObjectBox are very fast, comparable to clean SQLite or even faster, but have some downsides.
RealmObjects are live, auto-updating views into the underlying data; you never have to refresh objects. Changes to objects are instantly reflected in query results.
public class Dog extends RealmObject {
private String name;
private int age;
// ... Generated getters and setters ...
}
public class Person extends RealmObject {
@PrimaryKey
private long id;
@Index
private String name;
private RealmList<Dog> dogs; // Declare one-to-many relationships
@Ignore
private int sessionId;
// ... Generated getters and setters ...
}
Realm only manages the returned object, not the object originally created. To make changes to the object in the database, make changes to the returned copy, not the original.
// Initialize Realm (just once per application). Probaby in application subclass.
Realm.init(context);
// Get a Realm instance for this thread.
Realm realm = Realm.getDefaultInstance();
// No changes in the database yet. Object is unmanaged.
Dog dog = new Dog();
dog.setName("Rex");
// All writes are wrapped in a transaction to facilitate safe multi threading.
realm.beginTransaction();
final Dog managedDog = realm.copyToRealm(dog); // Persist unmanaged objects.
Person person = realm.createObject(Person.class); // Create managed objects directly.
person.setName("name"); // Database will be changed, because managed object changed.
person.getDogs().add(managedDog);
managedDog.deleteFromRealm();
realm.commitTransaction();
RealmResults<User> result = realm
.where(User.class)
.greaterThan("age", 10)
.beginGroup()
.equalTo("name", "Peter")
.or()
.contains("name", "Jo")
.endGroup()
.findAll();
// Automatically handles begin/commit, and cancel if an error happens.
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
Person person = realm.createObject(Person.class);
person.setName("name");
}
});
//Asynchronous transaction to avoid blocking UI.
realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm bgRealm) {
User user = bgRealm.createObject(User.class);
user.setName("John");
user.setEmail("[email protected]");
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
// Transaction was a success.
}
}, new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
// Transaction failed and was automatically canceled.
}
});
All fetches (including queries) are lazy in Realm, and the data is never copied.
// Build the query looking at all users.
RealmQuery<User> query = realm.where(User.class);
// Add query conditions.
query.equalTo("name", "John");
query.or().equalTo("name", "Peter");
// Execute the query. Method findAll executes the query.
RealmResults<User> result1 = query.findAll();
-
where method starts a RealmQuery by specifying a model.
-
findAll, findAllAsync, findFirst methods executes the query.
-
filter criteria is specified with predicate methods, most of which have self-explanatory names: equalTo, notEqualTo, in, between, greaterThan, lessThan, greaterThanOrEqualTo, lessThanOrEqualTo, contains, beginsWith, endsWith, like, isEmpty, isNotEmpty, isNull, isNotNull.
-
beginGroup and endGroup methods group conditions to specify order of evaluation.
-
not method negates conditions.
-
sort method specifies how the results should be sorted.
-
limit method limits query results.
-
distinct method limits results to only unique values.
UI or other looper threads can get notified of changes in a Realm by adding a listener, which is executed whenever the Realm is changed.
- Realm notifications.
realm = Realm.getDefaultInstance();
realmListener = new RealmChangeListener<Realm>() {
@Override
public void onChange(Realm realm) {
// ... do something with the updates (UI, etc.) ...
}
};
realm.addChangeListener(realmListener);
- Collection notifications.
persons.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<Person>>() {
@Override
public void onChange(RealmResults<Person> results, OrderedCollectionChangeSet changeSet) {
// ... do something with the updates (UI, etc.) ...
}
});
- Object notifications.
persons.addChangeListener(new RealmObjectChangeListener<Person>() {
@Override
public void onChange(Person person, ObjectChangeSet changeSet) {
// ... do something with the updates (UI, etc.) ...
}
};
-
The realm-android plugin has to be applied after kotlin-android and kotlin-kapt.
-
Model classes should be open.
-
A limitation of the Kotlin annotation processor indicates that adding the annotation @RealmClass is required in some cases.
-
Storing enums in Realm Model classes in Kotlin should be done using a specific pattern.
-
In Kotlin Long::class.java actually returns a Class reference to long not Long. The same is true for other primitive types like Integer, Float, Double and Boolean. Choosing the correct class has implications during a migration.
-
If Kotlin is used in a project, Realm automatically detects this and adds a number of extension methods that makes working with Kotlin easier.
The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access. Provides compile time verification of SQL queries. Can work with LiveData and RxJava. Part of the AAC - android architecture components.
dependencies {
implementation "androidx.room:room-runtime:2.1.0-beta01"
annotationProcessor "androidx.room:room-compiler:2.1.0-beta01"
// Old namespace.
// implementation "android.arch.persistence.room:runtime:1.1.1"
}
apply plugin: 'kotlin-kapt'
dependencies {
implementation "androidx.room:room-runtime:2.1.0-beta01"
kapt "androidx.room:room-compiler:2.1.0-beta01"
}
There are 3 major components in Room:
-
Database: Contains the database holder and serves as the main access point for the underlying connection to your app's persisted, relational data.
Abstract class that extends RoomDatabase. Annotated with @Database. Includes the list of entities within the annotation. Contains an abstract method that has 0 arguments and returns the class that is annotated with @Dao.
-
Entity: Represents a table within the database.
To persist a field, Room must have access to it. You can make a field public, or you can provide a getter and setter for it.
-
DAO: Contains the methods used for accessing the database.
Allow abstraction of the the database communication. Easier to mock in tests (compared to running direct sql queries). Automatically does the conversion from Cursor to application classes. All queries in Dao are verified while compile. Can be either an interface or an abstract class.
ObjectBox < Realm < GreenDAO < Room < SQLite < ORMLite.
Very questionable, depends on complexity of queries, read/write preferences, app architecture etc.
Realm and ObjectBox are very fast, comparable to clean SQLite or even faster, but have some downsides.
@Entity(tableName = "users")
public class User {
@PrimaryKey(autoGenerate = true)
public int uid;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
@Ignore
Bitmap picture;
}
@Dao
public interface UserDao {
@Query("SELECT * FROM users")
List<User> getAll();
@Query("SELECT * FROM users")
LiveData<List<User>> getAll();
@Query("SELECT * FROM users")
Observable<List<User>> getAll();
@Query("SELECT * FROM users WHERE uid IN (:userIds)")
List<User> loadAllByIds(int[] userIds);
@Query("SELECT * FROM users WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1")
User findByName(String first, String last);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(User... users);
@Insert
long insert(User user);
@Update
public void updateUsers(User... users);
@Delete
void delete(User user);
}
@Database(entities = {User.class, Book.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
public abstract BookDao bookDao();
}
Expensive, so probably singleton. Outside of the main thread. Use Room.inMemoryDatabaseBuilder() for in memory DB.
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
db.userDao().insert(user);
Specifies additional type converters that Room can use. The TypeConverter is added to the scope of the element so if you put it on a class / interface, all methods / fields in that class will be able to use the converters.
// example converter for java.util.Date
public static class DateConverter {
@TypeConverter
public Date fromLong(Long value) { return value == null ? null : new Date(value); }
@TypeConverter
public Long fromDate(Date date) { return date == null ? null : date.getTime() }
}
@Entity()
public class Employee {
@PrimaryKey
public long id;
@TypeConverters({DateConverter.class})
public Date date;
}
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE users "
+ " ADD COLUMN age INTEGER");
}
};
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name")
.addMigrations(MyDatabase.MIGRATION_1_2)
.build();
ObjectBox
https://docs.objectbox.io/
https://github.com/objectbox/objectbox-java
Realm
https://github.com/realm/realm-java
https://realm.io/docs/java/latest/
https://realm.io/blog/realm-for-android/
DBMS comparisons
https://github.com/Rexee/AndroidDatabaseLibraryComparison
https://github.com/AlexeyZatsepin/Android-ORM-benchmark
https://proandroiddev.com/android-databases-performance-crud-a963dd7bb0eb (recent)