Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Room. Which method of insertion will be faster?

I am using Room as an abstraction layer over SQLite. After reading this page I found out that we can insert multiple objects at the same time. Currently I use a For loop to insert objects, i.e one object in each For loop iteration. The two ways of inserting that I know of currently are:

  1. Using a For loop and inserting each object one at a time

    @Insert(onConflict = OnConflictStrategy.REPLACE) public void addActivity(Person person);

  2. Inserting an array or a list of objects.

    @Insert(onConflict = OnConflictStrategy.REPLACE) public void insertUsers(Person ... people);

When I was coding the insertion of objects, I did not know of the second way of insertion. Now I want to know if there is any noticeable difference in speeds between the two ways so that I can change my code to increase performance of my app.

like image 811
Dishonered Avatar asked May 17 '18 06:05

Dishonered


People also ask

What is insert method in Android?

androidx.room.Insert. Marks a method in a Dao annotated class as an insert method. The implementation of the method will insert its parameters into the database. All of the parameters of the Insert method must either be classes annotated with Entity or collections/array of it.

What is Dao in room?

android.arch.persistence.room.Dao. Marks the class as a Data Access Object. Data Access Objects are the main classes where you define your database interactions. They can include a variety of query methods. The class marked with @Dao should either be an interface or an abstract class.

What is the difference between SQLite and room database?

Room vs SQLiteRoom provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. In the case of SQLite, There is no compile-time verification of raw SQLite queries. But in Room, there is SQL validation at compile time.

Is Android room an ORM?

Is Android Room an ORM? Room isn't an ORM; instead, it is a whole library that allows us to create and manipulate SQLite databases more easily. By using annotations, we can define our databases, tables, and operations.


2 Answers

As requested by OP in a comment of their question, here's (for the sake of clarity, as an answer) what I did to check the performance:

Before, inserting objects one by one:

@Dao
abstract class MyDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    abstract fun insert(items: MyObject): Long
    // ...
}

Syncing code:

val response = backend.downloadItems() // download from server
val items = response.getData() // this is a List<MyObject>
if (items != null) {
    for (i in items) {
        myDao.persist(s)
    }
}

This took a minute on a Huawei P10+.

I changed this to:

@Dao
abstract class MyDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    abstract fun insert(items: Iterable<MyObject>)
    // ...
}

Syncing code:

val response = backend.downloadItems() // download from server
val items = response.getData() // this is a List<MyObject>
response.getData()?.let { myDao.insert(it) }

This took less than a second.

The point here is to use specifically the Iterable<> version of the DAO @Insert method, which as said by @iDemigod, uses the Iterable<> version of the EntityInsertionAdapter.

The body of said function is in @iDemigod's answer, and it uses a single prepared statement for all the insertions.

Parsing the SQL into a statement is expensive, and using a statement creates a transaction for the whole insert batch, which can help solve other issues (I had an observable LiveData<> on the database, which was notified 12k times during the insert... performance was awful).

like image 64
Benoit Duffez Avatar answered Sep 27 '22 18:09

Benoit Duffez


Under the hood Room generated classes are using EntityInsertionAdapter for this particular situation. And there is two methods, we need to check:

  1. This one is used for inserting a single entity

     public final long insertAndReturnId(T entity) {
        final SupportSQLiteStatement stmt = acquire();
        try {
            bind(stmt, entity);
            return stmt.executeInsert();
        } finally {
            release(stmt);
        }
    }
    
  2. While this one is used to insert an array of entities

    public final void insert(Iterable<T> entities) {
        final SupportSQLiteStatement stmt = acquire();
        try {
            for (T entity : entities) {
                bind(stmt, entity);
                stmt.executeInsert();
            }
        } finally {
            release(stmt);
        }
    }
    

AS you can see the internals are pretty much the same as yours - stmt.executeInsert(); is called once or in the loop. The only performance change using the insertUsers method I can think of is the change notification, which will happen only once, when all the users will be inserted. But if you're already doing you insertion in the loop wrapped with @Transaction then there would be no change.

like image 26
Demigod Avatar answered Sep 27 '22 17:09

Demigod