Accessing data using Room DAOs

When you use the Room persistence library to store your app's data, you interact with the stored data by defining data access objects, or DAOs. Each DAO includes methods that offer abstract access to your app's database. At compile time, Room automatically generates implementations of the DAOs that you define.

By using DAOs to access your app's database instead of query builders or direct queries, you can preserve separation of concerns, a critical architectural principle. DAOs also make it easier for you to mock database access when you test your app.

Anatomy of a DAO

You can define each DAO as either an interface or an abstract class. For basic use cases, you usually use an interface. In either case, you must always annotate your DAOs with @Dao. DAOs don't have properties, but they do define one or more methods for interacting with the data in your app's database.

The following code is an example of a simple DAO that defines methods for inserting, deleting, and selecting User objects in a Room database:

Kotlin

@Dao
interface UserDao {
    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)

    @Query("SELECT * FROM user")
    fun getAll(): List<User>
}

Java

@Dao
public interface UserDao {
    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);

    @Query("SELECT * FROM user")
    List<User> getAll();
}

There are two types of DAO methods that define database interactions:

  • Convenience methods that let you insert, update, and delete rows in your database without writing any SQL code.
  • Query methods that let you write your own SQL query to interact with the database.

The following sections demonstrate how to use both types of DAO methods to define the database interactions that your app needs.

Convenience methods

Room provides convenience annotations for defining methods that perform simple insertions, updates, and deletions without requiring you to write a SQL statement.

If you need to define more complex insertions, updates, or deletions, or if you need to query the data in the database, use a query method instead.

Insert

The @Insert annotation lets you define methods that insert their parameters into the appropriate table in the database. The following code shows examples of valid @Insert methods that insert one or more User objects into the database:

Kotlin

@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>)
}

Java

@Dao
public interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public void insertUsers(User... users);

    @Insert
    public void insertBothUsers(User user1, User user2);

    @Insert
    public void insertUsersAndFriends(User user, List<User> friends);
}

Each parameter for an @Insert method must be either an instance of a Room data entity class annotated with @Entity or a collection of data entity class instances, each of which points to a database. When an @Insert method is called, Room inserts each passed entity instance into the corresponding database table.

If the @Insert method receives a single parameter, it can return a long value, which is the new rowId for the inserted item. If the parameter is an array or a collection, then return an array or a collection of long values instead, with each value as the rowId for one of the inserted items. To learn more about returning rowId values, see the reference documentation for the @Insert annotation and the SQLite documentation for rowid tables.

Update

The @Update annotation lets you define methods that update specific rows in a database table. Like @Insert methods, @Update methods accept data entity instances as parameters. The following code shows an example of an @Update method that attempts to update one or more User objects in the database:

Kotlin

@Dao
interface UserDao {
    @Update
    fun updateUsers(vararg users: User)
}

Java

@Dao
public interface UserDao {
    @Update
    public void updateUsers(User... users);
}

Room uses the primary key to match passed entity instances to rows in the database. If there is no row with the same primary key, Room makes no changes.

An @Update method can optionally return an int value indicating the number of rows that were updated successfully.

Delete

The @Delete annotation lets you define methods that delete specific rows from a database table. Like @Insert methods, @Delete methods accept data entity instances as parameters. The following code shows an example of a @Delete method that attempts to delete one or more User objects from the database:

Kotlin

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

Java

@Dao
public interface UserDao {
    @Delete
    public void deleteUsers(User... users);
}

Room uses the primary key to match passed entity instances to rows in the database. If there is no row with the same primary key, Room makes no changes.

A @Delete method can optionally return an int value indicating the number of rows that were deleted successfully.

Query methods

The @Query annotation lets you write SQL statements and expose them as DAO methods. Use these query methods to query data from your app's database or when you need to perform more complex insertions, updates, and deletions.

Room validates SQL queries at compile time. This means that if there's a problem with your query, a compilation error occurs instead of a runtime failure.

Simple queries

The following code defines a method that uses a simple SELECT query to return all the User objects in the database:

Kotlin

@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>

Java

@Query("SELECT * FROM user")
public User[] loadAllUsers();

The following sections demonstrate how to modify this example for typical use cases.

Return a subset of a table's columns

Most of the time, you only need to return a subset of the columns from the table that you are querying. For example, your UI might display just the first and last name for a user instead of every detail about that user. To save resources and streamline your query's execution, only query the fields that you need.

Room lets you return a simple object from any of your queries as long as you can map the set of result columns onto the returned object. For example, you can define the following object to hold a user's first and last name:

Kotlin

data class NameTuple(
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?
)

Java

public class NameTuple {
    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    @NonNull
    public String lastName;
}

Then, you can return that simple object from your query method:

Kotlin

@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List<NameTuple>

Java

@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();

Room understands that the query returns values for the first_name and last_name columns and that these values can be mapped onto the fields in the NameTuple class. If the query returns a column that doesn't map onto a field in the returned object, Room displays a warning.

Pass simple parameters to a query

Most of the time, your DAO methods need to accept parameters so that they can perform filtering operations. Room supports using method parameters as bind parameters in your queries.

For example, the following code defines a method that returns all the users above a certain age:

Kotlin

@Query("SELECT * FROM user WHERE age > :minAge")
fun loadAllUsersOlderThan(minAge: Int): Array<User>

Java

@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);

You can also pass multiple parameters or reference the same parameter multiple times in a query, as demonstrated in the following code:

Kotlin

@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>

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

Java

@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

@Query("SELECT * FROM user WHERE first_name LIKE :search " +
       "OR last_name LIKE :search")
public List<User> findUserWithName(String search);

Pass a collection of parameters to a query

Some of your DAO methods might require you to pass in a variable number of parameters that is not known until runtime. Room understands when a parameter represents a collection and automatically expands it at runtime based on the number of parameters provided.

For example, the following code defines a method that returns information about all the users from a subset of regions:

Kotlin

@Query("SELECT * FROM user WHERE region IN (:regions)")
fun loadUsersFromRegions(regions: List<String>): List<User>

Java

@Query("SELECT * FROM user WHERE region IN (:regions)")
public List<User> loadUsersFromRegions(List<String> regions);

Query multiple tables

Some of your queries might require access to multiple tables to calculate the result. You can use JOIN clauses in your SQL queries to reference more than one table.

The following code defines a method that joins three tables together to return the books that are currently on loan to a specific user:

Kotlin

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

Java

@Query("SELECT * FROM book " +
       "INNER JOIN loan ON loan.book_id = book.id " +
       "INNER JOIN user ON user.id = loan.user_id " +
       "WHERE user.name LIKE :userName")
public List<Book> findBooksBorrowedByNameSync(String userName);

You can also define simple objects to return a subset of columns from multiple joined tables, as discussed in the Return a subset of a table's columns section. The following code defines a DAO with a method that returns the names of users and the names of the books that they have borrowed:

Kotlin

interface UserBookDao {
    @Query(
        "SELECT user.name AS userName, book.name AS bookName " +
        "FROM user, book " +
        "WHERE user.id = book.user_id"
    )
    fun loadUserAndBookNames(): LiveData<List<UserBook>>

    // You can also define this class in a separate file.
    data class UserBook(val userName: String?, val bookName: String?)
}

Java

@Dao
public interface UserBookDao {
   @Query("SELECT user.name AS userName, book.name AS bookName " +
          "FROM user, book " +
          "WHERE user.id = book.user_id")
   public LiveData<List<UserBook>> loadUserAndBookNames();

   // You can also define this class in a separate file, as long as you add the
   // "public" access modifier.
   static class UserBook {
       public String userName;
       public String bookName;
   }
}

Return a multimap

In Room 2.4 and higher, you can also query columns from multiple tables without defining an additional data class by writing query methods that return a multimap.

Consider the example from the Query multiple tables section. Instead of returning a list of instances of a custom data class that holds pairings of User and Book instances, you can return a mapping of User and Book directly from your query method:

Kotlin

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<User, List<Book>>

Java

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id"
)
public Map<User, List<Book>> loadUserAndBookNames();

When your query method returns a multimap, you can write queries that use GROUP BY clauses, letting you take advantage of SQL's capabilities for advanced calculations and filtering. For example, you can modify your loadUserAndBookNames() method to only return users with three or more books checked out:

Kotlin

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id" +
    "GROUP BY user.name WHERE COUNT(book.id) >= 3"
)
fun loadUserAndBookNames(): Map<User, List<Book>>

Java

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id" +
    "GROUP BY user.name WHERE COUNT(book.id) >= 3"
)
public Map<User, List<Book>> loadUserAndBookNames();

If you don't need to map entire objects, you can also return mappings between specific columns in your query by setting the keyColumn and valueColumn attributes in a @MapInfo annotation on your query method:

Kotlin

@MapInfo(keyColumn = "userName", valueColumn = "bookName")
@Query(
    "SELECT user.name AS username, book.name AS bookname FROM user" +
    "JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<String, List<String>>

Java

@MapInfo(keyColumn = "userName", valueColumn = "bookName")
@Query(
    "SELECT user.name AS username, book.name AS bookname FROM user" +
    "JOIN book ON user.id = book.user_id"
)
public Map<String, List<String>> loadUserAndBookNames();

Special return types

Room provides some special return types for integration with other API libraries.

Paginated queries with the Paging library

Room supports paginated queries through integration with the Paging library. In Room 2.3.0-alpha01 and higher, DAOs can return PagingSource objects for use with Paging 3.

Kotlin

@Dao
interface UserDao {
  @Query("SELECT * FROM users WHERE label LIKE :query")
  fun pagingSource(query: String): PagingSource<Int, User>
}

Java

@Dao
interface UserDao {
  @Query("SELECT * FROM users WHERE label LIKE :query")
  PagingSource<Integer, User> pagingSource(String query);
}

For more information about choosing type parameters for a PagingSource, see Select key and value types.

Direct cursor access

If your app's logic requires direct access to the return rows, you can write your DAO methods to return a Cursor object, as shown in the following example:

Kotlin

@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    fun loadRawUsersOlderThan(minAge: Int): Cursor
}

Java

@Dao
public interface UserDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    public Cursor loadRawUsersOlderThan(int minAge);
}

Additional resources

To learn more about accessing data using Room DAOs, see the following additional resources:

Samples

Codelabs