How does Room work internally?
Note: This article is part of the advanced Room series which covers all the details about the Room persistence library. You can read all the articles here:
- Introduction to Room Persistent Library in Android
- Data Access Objects - DAO in Room
- Entity Relationship in Room
- How does Room work internally? [You are here]
- Room Database Migrations
- Using Room with LiveData and other third-party libraries
So, let's get started.
In the previous articles, we discussed how we can use Room library (part of Google’s Jetpack project) to create relational persistence in Android applications. Room makes it very easy for a developer to setup a database and start using it in production.
In this article, we are going to focus on how Room accomplishes all these things.
You can find the project that is going to be used in the blog from here.
We are going to use this project as a reference for explaining how Room actually does everything. Following are the highlights of our project:
- It has a single database( UserDatabase ) which contains only one table/entity( User ).
@Database(entities = [User::class], version = 1)
abstract class UserDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
- User table has 3 columns: uid , first name and last name
@Entity(tableName = USERS_TABLE)
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = FIRST_NAME_COLUMN) val firstName: String?,
@ColumnInfo(name = LAST_NAME_COLUMN) val lastName: String?
)
- UserDao is the interface through which our application interacts with the database.
@Dao
interface UserDao {
@Query("SELECT * FROM $USERS_TABLE")
fun getAll(): List<User>
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
Internal Working of Room
After creating a Room Database, the first time you compile your code, Room autogenerates implementation of your
and
@Database
annotated classes. In the above example, implementation of
@Dao
and
UserDatabase
is autogenerated by Room annotation processor.
UserDao
Note: You can find the autogenerated code in build/generated/source/kapt/ folder.
In our example, the implementation of
is named as
UserDatabase
and implementation of
UserDatabase_Impl
is named as
UserDao
. These are the classes where actual processing happens. Let’s discuss both of the implementations individually.
UserDao_Impl
UserDatabase_Impl
An overview of UserDatabase_Impl looks like this:
public final class UserDatabase_Impl extends UserDatabase {
private volatile UserDao _userDao;
@Override
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
//Implementation
}
@Override
protected InvalidationTracker createInvalidationTracker() {
//Implementation
}
@Override
public void clearAllTables() {
//Implementation
}
@Override
public UserDao userDao() {
//Implementation
}
}
-
createOpenHelper()
Room.databaseBuilder().build()
SupportSQLiteOpenHelper
-
createInvalidationTracker()
-
clearAllTables()
-
userDao()
UserDao_Impl
users
UserDao_Impl
UserDao_Impl implements all the methods in UserDao. The overview of UserDao_Impl looks like this:
public final class UserDao_Impl implements UserDao {
private final RoomDatabase __db;
private final EntityInsertionAdapter<User> __insertionAdapterOfUser;
private final EntityDeletionOrUpdateAdapter<User> __deletionAdapterOfUser;
public UserDao_Impl(RoomDatabase __db) {
this.__db = __db;
this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
//Implementation
};
this.__deletionAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
//Implementation
};
}
@Override
public void insertAll(final User... users) {
//Implementation
}
@Override
public void delete(final User user) {
//Implementation
}
@Override
public List<User> getAll() {
//Implementation
}
@Override
public List<User> loadAllByIds(final int[] userIds) {
//Implementation
}
@Override
public User findByName(final String first, final String last) {
//Implementation
}
}
In the above example,
UserDao_Impl
has 3 fields:
__db, __insertionAdapterOfUser
and
__deletionAdapterOfUser.
- __db is an instance of RoomDatabase which is used for multiple purposes like transaction and querying the database.
-
__insertionAdapterOfUser
is an instance of EntityInsertionAdapter used for inserting entities into a table. This is used in the
insertAll()
-
__deletionAdapterOfUser
is an instance of EntityDeletionOrUpdateAdapter used to update/delete entities from a table. This is used in
delete()
Building the RoomDatabase
Till now, we have understood what happens after our project is successfully compiled. Also, we know that we need an instance of
which gives us an instance of
UserDatabase
in order to perform any database related operations.
UserDao
To get an instance of
Room provides us with a builder method named
UserDatabase
,
which gives us an instance of
Room.databaseBuilder
. We can use this instance to get
RoomDatabase.Builder
by invoking the
UserDatabase
method.
build()
val userDatabase = Room.databaseBuilder(
applicationContext,
UserDatabase::class.java,
"users-db"
).build()
We can use this builder to configure our database like
-
createFromAsset()
/createFromFile()
-
addMigrations()
-
allowMainThreadQueries()
-
fallbackToDestructiveMigration()
There are also many other methods provided in
for database configuration.
RoomDatabase.Builder
Once we invoke
method on this
build()
instance, Room validates and creates an instance of the autogenerated implementation of
RoomDatabase.Builder
— i.e.,
UserDatabase::
class
.java
. After the creation of
UserDatabase_Impl
,
UserDatabase_Impl
method is invoked on the database by passing the database configuration which in turn invokes the
init()
method of
createOpenHelper()
.Now we are going to discuss the implementation of some important methods in UserDatabase_Impl and UserDao_Impl discussed earlier.
UserDatabase_Impl
userDao() in
UserDatabase_Impl
UserDatabase_Impl
@Override
public UserDao userDao() {
if (_userDao != null) {
return _userDao;
} else {
synchronized(this) {
if(_userDao == null) {
_userDao = new UserDao_Impl(this);
}
return _userDao;
}
}
}
It lazily creates the implementation of
UserDao
— i.e.,
and returns it whenever
UserDao_Impl
is invoked. As we can see, it passes the instance of
userDao()
in
RoomDatabase
constructor.
UserDao_Impl
’s
insertAll() in
UserDao_Impl
UserDao_Impl
@Override
public void insertAll(final User... users) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__insertionAdapterOfUser.insert(users);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
It uses __db for creating the transaction and __insertionAdapterOfUser for insertion.
delete() in
UserDao_Impl
UserDao_Impl
@Override
public void delete(final User user) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__deletionAdapterOfUser.handle(user);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
It uses __db for creating the transaction and __deletionAdapterOfUser for deletion.
getAll() in
UserDao_Impl
UserDao_Impl
@Override
public List<User> getAll() {
final String _sql = "SELECT * FROM users";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
__db.assertNotSuspendingTransaction();
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
final int _cursorIndexOfFirstName = CursorUtil.getColumnIndexOrThrow(_cursor, "first_name");
final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "last_name");
final List<User> _result = new ArrayList<User>(_cursor.getCount());
while(_cursor.moveToNext()) {
final User _item;
final int _tmpUid;
_tmpUid = _cursor.getInt(_cursorIndexOfUid);
final String _tmpFirstName;
_tmpFirstName = _cursor.getString(_cursorIndexOfFirstName);
final String _tmpLastName;
_tmpLastName = _cursor.getString(_cursorIndexOfLastName);
_item = new User(_tmpUid,_tmpFirstName,_tmpLastName);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
_statement.release();
}
}
As we can see, it creates a
object from the query specified in
RoomSQLiteQuery
annotation. It then simply creates a
cursor
to fetch data from the database.
@Query
This is enough to get a basic understanding of how Room works internally. Hope you enjoyed this blog. In the next blog, we are going to learn Room Database Migrations .
You can also connect with me on LinkedIn , Twitter , Facebook and Github .
Thank You!!!