Data Access Objects - DAO in Room

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:

So, let's get started.

In previous articles, we have covered how we can use Room persistence library to create a relational database very easily. Some of the advantages of using Room are compile-time query verification, no boilerplate code and easy integration with RxJava, LiveData and Kotlin Coroutines. All these advantages in Room are achieved using Data Access Objects or DAOs.

In this article, we going to discuss Data Access Objects or DAOs in detail.

In Room, Data Access Objects or DAOs are used to access your application’s persisted data. They are a better and modular way to access your database as compared to query builders or direct queries.

A DAO can be either an interface or an abstract class. If it’s an abstract class, it can optionally have a constructor that takes a RoomDatabase as its only parameter. Room creates each DAO implementation at compile time.

You can perform multiple operations using DAO like Insertion, Updation, Deletion and making raw queries. Also, you can easily integrate LiveData, RxJava Observables, Kotlin Coroutines in DAOs.

Insertion

When you create a DAO method and annotate it with @Insert, Room generates an implementation that inserts all parameters into the database in a single transaction.

@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUsers(vararg users: User)    @Insert
    fun insertBothUsers(user1: User, user2: User)    @Insert
    fun insertUsersAndFriends(user: User, friends: List<User>)
}

onConflict annotation parameter signifies what to do if a conflict happens on insertion. It can take the following values:

  • OnConflictStrategy.REPLACE : To replace the old data and continue the transaction.
  • OnConflictStrategy.ROLLBACK : To rollback the transaction.
  • OnConflictStrategy.ABORT : To abort the transaction. The transaction is rolled back.
  • OnConflictStrategy.FAIL : To fail the transaction. The transaction is rolled back.
  • OnConflictStrategy.NONE : To ignore the conflict.
Note: ROLLBACK and FAIL strategies are deprecated. Use ABORT instead.

Updation

When you create a DAO method and annotate it with @Update, Room generates an implementation that modifies a set of entities, given as parameters, in the database. It uses a query that matches against the primary key of each entity.

@Dao
interface UserDao {
    @Update(onConflict = OnConflictStrategy.REPLACE)
    fun updateUsers(vararg users: User)    @Update
    fun update(user: User)
}

Deletion

When you create a DAO method and annotate it with @Delete, Room generates an implementation that removes a set of entities, given as parameters, from the database. It uses the primary keys to find the entities to delete.

@Dao
interface UserDao {
    @Delete
    fun deleteUsers(vararg users: User)
}

Simple queries

@Query is the main annotation used in DAO classes. It allows you to perform read/write operations on a database. Each @Query method is verified at compile time, so if there is a problem with the query, a compilation error occurs instead of a runtime failure.

Room also verifies the return value of the query such that if the name of the field in the returned object doesn’t match the corresponding column names in the query response, Room alerts you in one of the following two ways:

  • It gives a warning if only some field names match.
  • It gives an error if no field names match.
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun loadAllUsers(): Array<User>
}

Passing parameters into the query

Parameters passed to the DAO methods can be used into the query written in @Query annotation.

@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE age BETWEEN :minAge AND :maxAge")
    fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>

    @Query("SELECT * FROM users WHERE first_name LIKE :search " +
           "OR last_name LIKE :search")
    fun findUserWithName(search: String): List<User>
}

Returning subsets of columns

You can also return subsets of columns from a query in Room.

data class NameTuple(
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?
)@Dao
interface UserDao {
    @Query("SELECT first_name, last_name FROM users")
    fun loadFullName(): List<NameTuple>
}

Direct cursor access

If your app’s logic requires direct access to the return rows, you can return a Cursor object from your queries.

@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun loadAllUsers(): Cursor
}

Querying multiple tables

Some of your queries might require access to multiple tables to calculate the result. Room allows you to write any query, so you can also join tables. Furthermore, if the response is an observable data type, such as Flowable or LiveData, Room watches all tables referenced in the query for invalidation.

@Dao
interface BookDao {
    @Query(
        "SELECT * FROM book " +
        "INNER JOIN loan ON loan.book_id = book.id " +
        "INNER JOIN user ON user.id = loan.user_id " +
        "WHERE users.name LIKE :userName"
    )
    fun findBooksBorrowedByNameSync(userName: String): List<Book>
}

Query return types

Room supports a variety of return types for query methods, including specialised return types for interoperability with specific frameworks or APIs.

You can return LiveData, Observable and Flow from query methods. Also, you can make a DAO method suspend function. These are discussed in separate articles.

This is all about DAO in Room. Hope you enjoyed this blog. In the next blog, we will learn about Entity Relationship in Room.

You can also connect with me on LinkedIn, Twitter, Facebook and Github.

Thank You!!!