Android development requires modern solutions, a mobile applications store big amount of data.
SQLite project, a default Android database engine was launched in 2000 and has some alternatives like pretty popular Realm and quite new ObjectBox. Both products comprise NoSQL database and have a few things in common: DB built for objects, ACID properties, Multiversion Concurrency Control (MVCC) architecture, core written in C++, reactive data observation and a multiplatform.
Although they’re both blazing fast, their creators continue to make some tweaks so that the lib's are outstanding. Performance, features and usability are of paramount importance for devs. At present, Realm Java operates in version 3.5.0, whereas ObjectBox Java works in 0.9.13. Now, let me tell you something about transactions into db, which is a pretty interesting thing. What are the differences between the two libraries at hand? I'll explain that in a sec.
What do the two libraries have in common? They’re created for objects.
Adding new objects into an ObjectBox database looks blindingly obvious, but there are some things to keep in mind. Write transactions are expensive, so instead of putting every single object into a box:
public void addMultipleForeach() {
//Bad for performance
for (int i = 0; i < 10; i++) {
Item itemToAdd = new Item();
itemToAdd.setName("ItemBad " + System.currentTimeMillis());
itemBox.put(itemToAdd);
}
}
use this method to do it at once:
public void addMultipleBatch() {
//Good for performance
List<Item> itemsToAdd = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Item itemToAdd = new Item();
itemToAdd.setName("ItemGood " + System.currentTimeMillis());
itemsToAdd.add(itemToAdd);
}
itemBox.put(itemsToAdd);
}
Thanks to MVCC, read transactions can be done without blocking or waiting for write transactions. ObjectBox uses put() method with different overloads to insert objects into a db. Transactions are running implicitly by default, which is different to Realm. In more complex db tasks it’s recommended that you add an explicit transaction using one of the methods designed precisely for this task, e.g. RunInTx, runInReadTx, runInTxAsyncTx or callInTx.
public void addForDifferentBoxes() {
//Additional callInTxAsync returns callable, uses callInTx under the hood
//runInTxAsync runs runInTx under the hood
boxStore.runInTxAsync(() -> {
addMultipleBatch();
addMultipleNotesBatch();
}, (aVoid, throwable) -> {
//Empty
});
}
Explicit transactions have one big advantage over the implicit ones. Namely, you can use different boxes inside them, which means that you’re controlling the transaction boundary. Read transactions are cheap but have some drawbacks:
public void readTransactionsForeach(){
for (int i = 0; i<100; i++){
List<Item> items = itemBox.getAll();
}
}
Thanks to MVCC, many concurrent read transactions will no longer be burdened with waiting or blocking even when in progress. Write transactions are executed sequentially to ensure db consistency. In view of the above, it’s a bad idea to block write transactions for too long. One had better prepare objects which are about to be inserted in the first place.
Adding new objects in Realm is more complex, as developers have more control of the operations. Here's a couple of facts for the sake of knowledge: Read operations can be done at any time, they’re implicitly wrapped in transactions, i.e. in ObjectBox. Having committed a transaction, one should close() Realm instance. Otherwise, strange errors may occur. All write transactions have to be wrapped into explicit transactions!
try (Realm realm = Realm.getInstance(defaultConfiguration)) {
realm.beginTransaction();
realm.copyToRealm(itemToAdd);
realm.commitTransaction();
}
Write transactions can be committed or cancelled. Realm uses several methods with different overloads to insert objects into a db. Transactions are executed sequentially, which is why it’s good to use executeAsyncTransaction to prevent ANR.
try (Realm realm = Realm.getInstance(defaultConfiguration)) {
realm.executeTransactionAsync(realm1 ->
realm1.copyToRealm(itemToAdd));
}
Importantly, async transaction callbacks are allowed only on a Looper thread (Thread without Looper disallows updates in the db). If a dev uses realm.executeTransaction instead of handling transactions manually, it's the Realm that takes care of potential errors and cancels transactions.
try (Realm realm = Realm.getInstance(defaultConfiguration)) {
try {
realm.beginTransaction();
realm.copyToRealm(itemToAdd);
realm.commitTransaction();
} catch (Exception e) {
//Cancel manually if any error
realm.cancelTransaction();
}
}
Inserting object is done by Realm.createObject(), Realm.copyToRealm(), Realm.copyToRealmOrUpdate(), Realm.createAllFromJson(), Realm.createObjectFromJson(), Realm.createOrUpdateAllFromJson(), Realm.createOrUpdateObjectFromJson(), Realm.insert(), Realm.insertOrUpdate(). Compared to ObjectBox that has only one method with different overloads, a variety of different methods in Realm may seem overwhelming at a first glance, but as I've mentioned, a dev has more control than he needs.
I’ve tested the time of inserting simple objects containing only primary key and String field name into a db. I’ve used MacBook Pro with CPU 2,5 GHz Intel Core i7 and 16 GB 1600 MHz DDR3 of RAM as well as Nexus 6P API 23 emulator.
Code to insert objects:
try (Realm realm = Realm.getInstance(defaultConfiguration)) {
realm.executeTransactionAsync(realm1 -> {
realm1.insert(itemsToAdd);
}
, () -> {
//Difference calculation
});
}
boxStore.runInTxAsync(() -> {
itemBox.put(itemsToAdd);
}, (aVoid, throwable) -> {
//Difference calculation
});
The charts show that ObjectBox is faster when it comes to inserting simple objects and the difference is massive. The more objects to insert, the larger the difference.
Both libraries respect ACID, are MVCC and work on objects. However, the approach to the transaction is at variance. ObjectBox is easy to use and offers limited options along with write transactions that are implicit by default. Realm, in turn, exhibits more options from which to choose a better fit for a particular dev's case. Both libraries seem like a good alternative for SQLite, but the decision which one to use should be in sync with your individual needs.