Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

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

Room 지속성 라이브러리를 사용하여 앱 데이터에 액세스하려면 데이터 액세스 객체 또는 DAO를 사용하세요. 각 DAO에는 앱 데이터베이스의 추상 액세스를 제공하는 메서드가 포함되어 있으므로 이 Dao 객체 세트는 Room의 기본 구성요소를 형성합니다.

쿼리 빌더 또는 직접 쿼리 대신 DAO 클래스를 사용하여 데이터베이스에 액세스하면 데이터베이스 아키텍처의 다양한 구성요소를 분리할 수 있습니다. 또한 DAO를 사용하면 앱을 테스트할 때 데이터베이스 액세스를 쉽게 모의 테스트할 수 있습니다.

DAO는 인터페이스 또는 추상 클래스일 수 있습니다. 추상 클래스라면 RoomDatabase를 유일한 매개변수로 사용하는 생성자를 선택적으로 가질 수 있습니다. Room은 컴파일 시간에 각 DAO 구현을 생성합니다.

참고: 빌더에서 allowMainThreadQueries()를 호출하지 않으면 Room은 기본 스레드에서 데이터베이스 액세스를 지원하지 않습니다. UI를 오랫동안 잠글 수 있기 때문입니다. LiveData 또는 Flowable의 인스턴스를 반환하는 비동기 쿼리는 필요한 경우 백그라운드 스레드에서 쿼리를 비동기식으로 실행하므로 이 규칙에서 제외됩니다.

편의를 위한 메서드 정의

DAO 클래스를 사용하여 나타낼 수 있는 여러 편의성 쿼리가 있습니다. 이 문서에는 일반적인 예가 나와 있습니다.

삽입

DAO 메서드를 만들고 @Insert 주석을 달 때 Room은 단일 트랜잭션으로 모든 매개변수를 데이터베이스에 삽입하는 구현을 생성합니다.

다음 코드 스니펫은 몇 가지 쿼리 예를 보여줍니다.

Kotlin

    @Dao
    interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun insertUsers(vararg users: User)

        @Insert
        fun insertBothUsers(user1: User, user2: User)

        @Insert
        fun insertUsersAndFriends(user: User, friends: List<User>)
    }
    

자바

    @Dao
    public interface MyDao {
        @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 메서드는 매개변수를 하나만 수신하면 삽입된 항목의 새 rowIdlong을 반환할 수 있습니다. 매개변수가 배열 또는 컬렉션이면 대신 long[] 또는 List<Long>을 반환해야 합니다.

자세한 내용은 @Insert 주석에 관한 참조 문서와 rowid 테이블에 관한 SQLite 문서를 참조하세요.

업데이트

Update 편의 메서드는 매개변수로 제공된 항목 세트를 데이터베이스에서 수정합니다. 이 메서드는 각 항목의 기본 키와 일치하는 쿼리를 사용합니다.

다음 코드 스니펫은 이 메서드를 정의하는 방법을 보여줍니다.

Kotlin

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

자바

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

일반적으로 필요하지는 않지만 이 메서드가 int 값을 대신 반환하도록 하여 데이터베이스에서 업데이트된 행 수를 나타낼 수 있습니다.

삭제

Delete 편의 메서드는 매개변수로 제공된 항목 세트를 데이터베이스에서 삭제합니다. 이 메서드는 기본 키를 사용하여 삭제할 항목을 찾습니다.

다음 코드 스니펫은 이 메서드를 정의하는 방법을 보여줍니다.

Kotlin

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

자바

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

일반적으로 필요하지는 않지만 이 메서드가 int 값을 대신 반환하도록 하여 데이터베이스에서 삭제된 행 수를 나타낼 수 있습니다.

정보 쿼리

@Query는 DAO 클래스에서 사용하는 기본 주석입니다. 이 주석으로 데이터베이스에서 읽기/쓰기 작업을 실행할 수 있습니다. 각 @Query 메서드는 컴파일 시간에 확인되므로 쿼리에 문제가 있으면 런타임 오류 대신 컴파일 오류가 발생합니다.

또한 Room은 반환된 개체의 필드 이름이 쿼리 응답의 열 이름과 일치하지 않더라도 쿼리의 반환 값을 확인합니다. Room은 다음 두 가지 방법 중 하나로 경고합니다.

  • 일부 필드 이름만 일치하면 경고를 표시합니다.
  • 일치하는 필드 이름이 없으면 오류를 표시합니다.

단순한 쿼리

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT * FROM user")
        fun loadAllUsers(): Array<User>
    }
    

자바

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user")
        public User[] loadAllUsers();
    }
    

이 쿼리는 모든 사용자를 로드하는 매우 단순한 쿼리입니다. 컴파일 타임에 Room은 사용자 테이블의 모든 열을 쿼리하고 있음을 알고 있습니다. 쿼리에 구문 오류가 포함되어 있거나 데이터베이스에 사용자 테이블이 없으면 앱이 컴파일될 때 Room은 적절한 메시지와 함께 오류를 표시합니다.

쿼리에 매개변수 전달

대부분의 경우 특정 연령보다 나이가 더 많은 사용자만 표시하는 식의 필터링 작업을 실행하려면 매개변수를 쿼리에 전달해야 합니다. 이 작업을 실행하려면 다음 코드 스니펫에서와 같이 Room 주석에 메서드 매개변수를 사용하세요.

Kotlin

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

자바

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

이 쿼리가 컴파일 시간에 처리되면 Room은 :minAge 바인드 매개변수를 minAge 메서드 매개변수와 일치시킵니다. Room은 매개변수 이름을 사용하여 일치를 실행합니다. 불일치가 있으면 앱이 컴파일될 때 오류가 발생합니다.

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

Kotlin

    @Dao
    interface MyDao {
        @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>
    }
    

자바

    @Dao
    public interface MyDao {
        @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);
    }
    

열의 하위 세트 반환

대부분의 경우 항목의 몇 가지 필드만 가져와야 합니다. 예를 들어 UI에는 사용자에 관한 모든 세부정보가 아닌 사용자의 이름 및 성만 표시될 수 있습니다. 앱 UI에 표시되는 열만 가져오면 귀중한 리소스가 절약되고 쿼리도 더 빨리 완료될 수 있습니다.

Room을 사용하면 결과 열 세트를 반환된 개체에 매핑할 수 있는 한 쿼리에서 어떤 자바 기반 개체라도 반환할 수 있습니다. 예를 들어 다음과 같은 POJO(Plain Old Java-based Object)를 생성하여 사용자의 이름 및 성을 가져올 수 있습니다.

Kotlin

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

자바

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

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

이제 쿼리 메서드에서 이 POJO를 사용할 수 있습니다.

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT first_name, last_name FROM user")
        fun loadFullName(): List<NameTuple>
    }
    

자바

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user")
        public List<NameTuple> loadFullName();
    }
    

Room은 쿼리에서 first_namelast_name 열의 값을 반환하고 이러한 값은 NameTuple 클래스의 필드에 매핑될 수 있다는 것을 인식합니다. 따라서 Room은 적절한 코드를 생성할 수 있습니다. 쿼리에서 너무 많은 열을 반환하거나 NameTuple 클래스에 없는 열을 반환하면 Room은 경고를 표시합니다.

인수 컬렉션 전달

일부 쿼리에서는 런타임까지 알려지지 않는 정확한 수의 매개변수와 함께 가변적인 수의 매개변수를 전달해야 할 수도 있습니다. 예를 들어 지역의 하위 세트에서 모든 사용자에 관한 정보를 검색하려고 할 수 있습니다. Room은 매개변수가 언제 컬렉션을 나타내는지 인식하고 제공된 매개변수 수에 따라 런타임 시에 자동으로 확장합니다.

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        fun loadUsersFromRegions(regions: List<String>): List<NameTuple>
    }
    

자바

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        public List<NameTuple> loadUsersFromRegions(List<String> regions);
    }
    

직접 커서 액세스

앱의 로직에서 직접 반환 행에 액세스해야 한다면 다음 코드 스니펫과 같이 쿼리에서 Cursor 객체를 반환할 수 있습니다.

Kotlin

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

자바

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

주의: Cursor API를 사용하여 작업하지 않는 것이 좋습니다. 행의 존재 여부 또는 행에 포함된 값을 보장하지 않기 때문입니다. 커서를 예상하는 코드 및 쉽게 리팩터링할 수 없는 코드가 이미 있는 경우에만 이 기능을 사용하세요.

여러 테이블 쿼리

일부 쿼리는 결과를 계산하기 위해 여러 테이블에 액세스해야 할 수 있습니다. Room을 사용하면 어떤 쿼리든 작성할 수 있으므로 테이블을 조인할 수도 있습니다. 또한 응답이 Flowable 또는 LiveData와 같은 식별 가능한 데이터 유형이면 Room은 쿼리에서 참조된 모든 테이블의 무효화를 관찰합니다.

다음 코드 스니펫은 테이블 조인을 실행하여 책을 빌리는 사용자가 포함된 테이블과 현재 대출 중인 책에 관한 데이터가 포함된 테이블 간에 정보를 통합하는 방법을 보여줍니다.

Kotlin

    @Dao
    interface MyDao {
        @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>
    }
    

자바

    @Dao
    public interface MyDao {
        @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);
    }
    

또한 이러한 쿼리에서 POJO를 반환할 수도 있습니다. 예를 들어 다음과 같이 사용자와 애완동물 이름을 로드하는 쿼리를 작성할 수 있습니다.

Kotlin

    @Dao
    interface MyDao {
        @Query(
            "SELECT user.name AS userName, pet.name AS petName " +
            "FROM user, pet " +
            "WHERE user.id = pet.user_id"
        )
        fun loadUserAndPetNames(): LiveData<List<UserPet>>

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

자바

    @Dao
    public interface MyDao {
       @Query("SELECT user.name AS userName, pet.name AS petName " +
              "FROM user, pet " +
              "WHERE user.id = pet.user_id")
       public LiveData<List<UserPet>> loadUserAndPetNames();

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

쿼리 반환 유형

Room은 특정 프레임워크나 API와의 상호 운용성을 위한 특수한 반환 유형을 비롯하여 쿼리 메서드에 다양한 반환 유형을 지원합니다. 다음 표는 쿼리 유형 및 프레임워크에 기반한 관련 반환 유형을 보여줍니다.

쿼리 유형 코루틴 RxJava Guava Lifecycle
식별 가능한 읽기 Flow<T> Flowable<T>, Publisher<T>, Observable<T> N/A LiveData<T>
원샷 읽기 suspend fun Maybe<T> Single<T> ListenableFuture<T> N/A
원샷 쓰기 suspend fun Single<T>, Maybe<T>, Completable<T> ListenableFuture<T> N/A

흐름을 사용하는 반응형 쿼리

Room 2.2 이상에서는 Kotlin의 Flow 기능을 사용하여 앱의 UI를 최신 상태로 유지할 수 있습니다. 기본 데이터가 변경될 때 UI가 자동으로 업데이트되도록 하려면 Flow 객체를 반환하는 쿼리 메서드를 작성하세요.

@Query("SELECT * FROM User")
    fun getAllUsers(): Flow<List<User>>
    

테이블의 데이터가 변경될 때마다 반환된 Flow 객체가 쿼리를 다시 트리거하고 전체 결과 세트를 다시 내보냅니다.

Flow를 사용하는 반응형 쿼리에는 한 가지 중요한 제한사항이 있습니다. Flow 객체는 행이 결과 세트에 있든 없든 테이블의 행이 업데이트될 때마다 쿼리를 다시 실행합니다. distinctUntilChanged() 연산자를 반환된 Flow 객체에 적용하여 실제 쿼리 결과가 변경될 때만 UI에 알림을 보낼 수 있습니다.

@Dao
    abstract class UsersDao {
        @Query("SELECT * FROM User WHERE username = :username")
        abstract fun getUser(username: String): Flow<User>

        fun getUserDistinctUntilChanged(username:String) =
               getUser(username).distinctUntilChanged()
    }
    

Kotlin 코루틴으로 비동기 쿼리

Kotlin 코루틴 기능을 사용해 suspend Kotlin 키워드를 DAO 메서드에 추가하여 메서드를 비동기식으로 설정할 수 있습니다. 이렇게 하면 메인 스레드에서 이러한 메서드를 실행할 수 없습니다.

@Dao
    interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        suspend fun insertUsers(vararg users: User)

        @Update
        suspend fun updateUsers(vararg users: User)

        @Delete
        suspend fun deleteUsers(vararg users: User)

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

이 지침은 @Transaction 주석이 달린 DAO 메서드에도 적용됩니다. 이 기능을 사용하여 다른 DAO 메서드에서 보류 중인 데이터베이스 메서드를 빌드할 수 있습니다. 이러한 메서드는 단일 데이터베이스 트랜잭션에서 실행됩니다.

@Dao
    abstract class UsersDao {
        @Transaction
        open suspend fun setLoggedInUser(loggedInUser: User) {
            deleteUser(loggedInUser)
            insertUser(loggedInUser)
        }

        @Query("DELETE FROM users")
        abstract fun deleteUser(user: User)

        @Insert
        abstract suspend fun insertUser(user: User)
    }
    

앱에서 Kotlin 코루틴을 사용하는 방법에 관한 자세한 내용은 Kotlin 코루틴으로 앱 성능 향상을 참조하세요.

LiveData로 식별 가능한 쿼리

쿼리를 실행할 때 대개 데이터 변경 시 앱 UI가 자동으로 업데이트되기를 원할 것입니다. 이러한 작업을 실행하려면 쿼리 메서드 설명에서 LiveData 유형의 반환 값을 사용하세요. 데이터베이스가 업데이트될 때 Room에서 LiveData를 업데이트하는 데 필요한 모든 코드를 생성합니다.

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        fun loadUsersFromRegionsSync(regions: List<String>): LiveData<List<User>>
    }
    

자바

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
    }
    

RxJava를 사용하는 반응형 쿼리

Room은 다음과 같은 RxJava2 유형의 반환 값을 지원합니다.

이 기능을 사용하려면 최신 버전의 rxjava2 아티팩트를 앱의 build.gradle 파일에 포함하세요.

app/build.gradle

    dependencies {
        def room_version = "2.1.0"
        implementation 'androidx.room:room-rxjava2:$room_version'
    }
    

이 라이브러리의 현재 버전을 보려면 버전 페이지에서 Room 관련 정보를 확인하세요.

다음 코드 스니펫은 이러한 반환 유형을 사용할 수 있는 방법에 관한 몇 가지 예를 보여줍니다.

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT * from user where id = :id LIMIT 1")
        fun loadUserById(id: Int): Flowable<User>

        // Emits the number of users added to the database.
        @Insert
        fun insertLargeNumberOfUsers(users: List<User>): Maybe<Int>

        // Makes sure that the operation finishes successfully.
        @Insert
        fun insertLargeNumberOfUsers(varargs users: User): Completable

        /* Emits the number of users removed from the database. Always emits at
           least one user. */
        @Delete
        fun deleteAllUsers(users: List<User>): Single<Int>
    }
    

자바

    @Dao
    public interface MyDao {
        @Query("SELECT * from user where id = :id LIMIT 1")
        public Flowable<User> loadUserById(int id);

        // Emits the number of users added to the database.
        @Insert
        public Maybe<Integer> insertLargeNumberOfUsers(List<User> users);

        // Makes sure that the operation finishes successfully.
        @Insert
        public Completable insertLargeNumberOfUsers(User... users);

        /* Emits the number of users removed from the database. Always emits at
           least one user. */
        @Delete
        public Single<Integer> deleteUsers(List<User> users);
    }
    

자세한 내용은 Google Developers Room 및 RxJava 자료를 참조하세요.

추가 리소스

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

샘플

Codelab

블로그