Room DAO를 사용하여 데이터 액세스

Room 지속성 라이브러리를 사용하여 앱 데이터를 저장할 때 데이터 액세스 객체(DAO)를 정의하여 저장된 데이터와 상호작용합니다. 각 DAO에는 앱 데이터베이스에 관한 추상 액세스 권한을 제공하는 메서드가 포함되어 있습니다. Room은 컴파일 시간에 정의된 DAO 구현을 자동으로 생성합니다.

쿼리 빌더나 직접 쿼리 대신 DAO를 사용하여 앱 데이터베이스에 액세스하면 중요한 아키텍처 원칙인 관심사 분리를 유지할 수 있습니다. DAO를 사용하면 앱을 테스트할 때 데이터베이스 액세스도 더 쉽게 모의 처리할 수 있습니다.

DAO 분석

각 DAO를 인터페이스나 추상 클래스로 정의할 수 있습니다. 기본 사용 사례에서는 일반적으로 인터페이스를 사용합니다. 어느 경우든 DAO에 항상 @Dao 주석을 달아야 합니다. DAO에는 속성이 없지만 앱 데이터베이스의 데이터와 상호작용하는 메서드를 하나 이상 정의합니다.

다음 코드는 Room 데이터베이스에서 User 객체를 삽입, 삭제, 선택하는 메서드를 정의하는 간단한 DAO 예입니다.

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();
}

데이터베이스 상호작용을 정의하는 DAO 메서드에는 두 가지 유형이 있습니다.

  • 편의 메서드: SQL 코드 작성 없이 데이터베이스에서 행을 삽입하고 업데이트하고 삭제할 수 있습니다.
  • 쿼리 메서드: 자체 SQL 쿼리를 작성하여 데이터베이스와 상호작용할 수 있습니다.

다음 섹션에서는 두 가지 유형의 DAO 메서드를 모두 사용하여 앱에 필요한 데이터베이스 상호작용을 정의하는 방법을 보여줍니다.

편의 메서드

Room은 개발자가 SQL 문을 작성하지 않아도 간단한 삽입과 업데이트, 삭제를 실행하는 메서드를 정의하는 편의 주석을 제공합니다.

좀 더 복잡한 삽입이나 업데이트, 삭제를 정의해야 하거나 데이터베이스의 데이터를 쿼리해야 하면 대신 쿼리 메서드를 사용하세요.

삽입

@Insert 주석을 사용하면 데이터베이스의 적절한 테이블에 매개변수를 삽입하는 메서드를 정의할 수 있습니다. 다음 코드는 데이터베이스에 User 객체를 하나 이상 삽입하는 유효한 @Insert 메서드의 예를 보여줍니다.

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

@Insert 메서드의 각 매개변수는 @Entity 주석이 달린 Room 데이터 항목 클래스의 인스턴스이거나 데이터 항목 클래스 인스턴스의 컬렉션이어야 합니다(각각 데이터베이스를 가리킴). @Insert 메서드가 호출되면 Room은 전달된 각 항목 인스턴스를 상응하는 데이터베이스 테이블에 삽입합니다.

@Insert 메서드가 단일 매개변수를 수신하면 long 값을 반환할 수 있고 이 값은 삽입된 항목의 새 rowId입니다. 매개변수가 배열이나 컬렉션이면 long 값의 배열이나 컬렉션을 대신 반환하며, 각 값은 삽입된 항목 중 하나의 rowId입니다. rowId 값 반환에 관한 자세한 내용은 @Insert 주석 참조 문서와 rowid 테이블 SQLite 문서를 참고하세요.

업데이트

@Update 주석을 사용하면 데이터베이스 테이블에서 특정 행을 업데이트하는 메서드를 정의할 수 있습니다. @Insert 메서드와 마찬가지로 @Update 메서드는 데이터 항목 인스턴스를 매개변수로 허용합니다. 다음 코드는 데이터베이스에서 User 객체를 하나 이상 업데이트하려고 하는 @Update 메서드의 예를 보여줍니다.

Kotlin

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

Java

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

Room은 기본 키를 사용하여 전달된 항목 인스턴스를 데이터베이스의 행과 일치시킵니다. 기본 키가 같은 행이 없으면 Room에서는 아무것도 변경하지 않습니다.

@Update 메서드는 성공적으로 업데이트된 행 수를 나타내는 int 값을 선택적으로 반환할 수 있습니다.

삭제

@Delete 주석을 사용하면 데이터베이스 테이블에서 특정 행을 삭제하는 메서드를 정의할 수 있습니다. @Insert 메서드와 마찬가지로 @Delete 메서드는 데이터 항목 인스턴스를 매개변수로 허용합니다. 다음 코드는 데이터베이스에서 User 객체를 하나 이상 삭제하려고 하는 @Delete 메서드의 예를 보여줍니다.

Kotlin

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

Java

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

Room은 기본 키를 사용하여 전달된 항목 인스턴스를 데이터베이스의 행과 일치시킵니다. 기본 키가 같은 행이 없으면 Room에서는 아무것도 변경하지 않습니다.

@Delete 메서드는 성공적으로 삭제된 행 수를 나타내는 int 값을 선택적으로 반환할 수 있습니다.

쿼리 메서드

@Query 주석을 사용하면 SQL 문을 작성하여 DAO 메서드로 노출할 수 있습니다. 이러한 쿼리 메서드는 앱 데이터베이스에서 데이터를 쿼리하거나 좀 더 복잡한 삽입, 업데이트, 삭제를 실행해야 할 때 사용합니다.

Room은 컴파일 시간에 SQL 쿼리를 검증합니다. 즉, 쿼리에 문제가 있으면 런타임 실패가 아닌 컴파일 오류가 발생합니다.

단순한 쿼리

다음 코드는 간단한 SELECT 쿼리를 사용하여 데이터베이스의 User 객체를 모두 반환하는 메서드를 정의합니다.

Kotlin

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

Java

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

다음 섹션에서는 일반적인 사용 사례에서 이 예를 수정하는 방법을 보여줍니다.

테이블 열의 하위 집합 반환

대부분의 경우 쿼리하는 테이블에서 열의 하위 집합만 반환하면 됩니다. 예를 들어 UI에서는 사용자에 관한 모든 세부정보 대신 사용자의 성과 이름만 표시할 수 있습니다. 리소스를 절약하고 쿼리 실행을 간소화하려면 필요한 필드만 쿼리합니다.

Room을 사용하면 결과 열 세트를 반환된 객체에 매핑할 수 있는 한 모든 쿼리에서 간단한 객체를 반환할 수 있습니다. 예를 들어 사용자의 성과 이름을 보유하도록 다음 객체를 정의할 수 있습니다.

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;
}

그런 다음 쿼리 메서드에서 이 간단한 객체를 반환할 수 있습니다.

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은 쿼리에서 first_namelast_name 열의 값을 반환하고 이러한 값은 NameTuple 클래스의 필드에 매핑될 수 있다는 것을 인식합니다. 쿼리가 반환된 객체의 필드에 매핑되지 않는 열을 반환하면 Room에서는 경고를 표시합니다.

쿼리에 단순 매개변수 전달

대부분의 경우 DAO 메서드는 필터링 작업을 실행할 수 있도록 매개변수를 허용해야 합니다. Room은 쿼리에서 메서드 매개변수를 결합 매개변수로 사용하는 것을 지원합니다.

예를 들어 다음 코드는 특정 연령 이상의 사용자를 모두 반환하는 메서드를 정의합니다.

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);

다음 코드와 같이 쿼리에서 여러 매개변수를 전달하거나 같은 매개변수를 여러 번 참조할 수도 있습니다.

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);

쿼리에 매개변수 컬렉션 전달

일부 DAO 메서드를 사용하면 런타임까지 알 수 없는 가변적인 수의 매개변수를 전달해야 할 수 있습니다. Room은 매개변수가 언제 컬렉션을 나타내는지 인식하고 제공된 매개변수 수에 따라 런타임 시에 자동으로 확장합니다.

예를 들어 다음 코드는 일부 리전에서 모든 사용자에 관한 정보를 반환하는 메서드를 정의합니다.

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);

여러 테이블 쿼리

일부 쿼리는 결과를 계산하기 위해 여러 테이블에 액세스해야 할 수 있습니다. SQL 쿼리에서 JOIN 절을 사용하여 테이블을 두 개 이상 참조할 수 있습니다.

다음 코드는 테이블 세 개를 함께 조인하여 현재 특정 사용자에게 대출 중인 책을 반환하는 메서드를 정의합니다.

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);

테이블 열의 하위 집합 반환 섹션에 설명된 대로 여러 조인된 테이블에서 열 하위 집합을 반환하도록 간단한 객체를 정의할 수도 있습니다. 다음 코드는 사용자 이름과 사용자가 빌린 책 제목을 반환하는 메서드를 사용하여 DAO를 정의합니다.

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;
   }
}

멀티매핑 반환

Room 2.4 이상에서는 멀티매핑을 반환하는 쿼리 메서드를 작성하여 추가 데이터 클래스를 정의하지 않고도 여러 테이블의 열을 쿼리할 수도 있습니다.

여러 테이블 쿼리 섹션의 예를 참고하세요. User 인스턴스와 Book 인스턴스의 쌍을 보유하는 맞춤 데이터 클래스의 인스턴스 목록을 반환하는 대신 쿼리 메서드에서 UserBook의 매핑을 직접 반환할 수 있습니다.

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();

쿼리 메서드가 멀티매핑을 반환하면 GROUP BY 절을 사용하는 쿼리를 작성할 수 있으므로 SQL의 고급 계산 및 필터링 기능을 활용할 수 있습니다. 예를 들어 책을 3권 이상 대출한 사용자만 반환하도록 loadUserAndBookNames() 메서드를 수정할 수 있습니다.

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();

전체 객체를 매핑하지 않아도 되면 쿼리 메서드의 @MapInfo 주석에서 keyColumnvalueColumn 속성을 설정하여 쿼리의 특정 열 간 매핑을 반환할 수도 있습니다.

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();

특수 반환 유형

Room은 다른 API 라이브러리와의 통합을 위해 특수 반환 유형을 제공합니다.

Paging 라이브러리를 사용하여 페이지로 나눈 쿼리

Room은 Paging 라이브러리와의 통합을 통해 페이지로 나눈 쿼리를 지원합니다. Room 2.3.0-alpha01 이상에서 DAO는 Paging 3과 함께 사용하도록 PagingSource 객체를 반환할 수 있습니다.

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

PagingSource의 유형 매개변수 선택에 관한 자세한 내용은 키 및 값 유형 선택을 참고하세요.

직접 커서 액세스

앱 로직에서 반환 행에 직접 액세스해야 한다면 다음 예와 같이 DAO 메서드를 작성하여 Cursor 객체를 반환할 수 있습니다.

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

추가 리소스

Room DAO를 사용하는 데이터 액세스에 관한 자세한 내용은 다음 추가 리소스를 참고하세요.

샘플

Codelab