비동기 DAO 쿼리 작성

쿼리가 UI를 차단하지 않도록 Room은 기본 스레드에서 데이터베이스 액세스를 지원하지 않습니다. 이 제한사항은 DAO 쿼리 를 비동기식으로 만들어야 함을 의미합니다. Room 라이브러리에는 비동기 쿼리 실행을 제공하는 여러 프레임워크와의 통합이 포함되어 있습니다.

DAO 쿼리는 세 가지 카테고리로 분류됩니다.

  • 원샷 쓰기 쿼리: 데이터베이스에서 데이터를 삽입하거나 업데이트하거나 삭제합니다.
  • 원샷 읽기 쿼리: 데이터베이스에서 데이터를 한 번만 읽고 그 시점의 데이터베이스 스냅샷과 함께 결과를 반환합니다.
  • 관찰 가능한 읽기 쿼리: 기본 데이터베이스 테이블이 변경될 때마다 데이터베이스에서 데이터를 읽고 이러한 변경사항을 반영하는 새 값을 내보냅니다.

언어 및 프레임워크 옵션

Room은 특정 언어 기능 및 라이브러리와의 상호 운용성을 위한 통합 지원을 제공합니다. 다음 표는 쿼리 유형 및 프레임워크에 기반한 관련 반환 유형을 보여줍니다.

쿼리 유형 Kotlin 언어 기능 (네이티브) RxJava Guava Jetpack 수명 주기*
원샷 쓰기 코루틴(suspend) Single<T>, Maybe<T>, Completable ListenableFuture<T> 해당 사항 없음
원샷 읽기 코루틴(suspend) Single<T>, Maybe<T> ListenableFuture<T> 해당 사항 없음
관찰 가능한 읽기 Flow<T> Flowable<T>, Publisher<T>, Observable<T> 해당 사항 없음 LiveData<T>

이 가이드에서는 이러한 통합을 사용하여 DAO에서 비동기 쿼리를 구현하는 세 가지 방법을 보여줍니다.

Flow 및 코루틴을 사용하는 Kotlin

Kotlin은 서드 파티 프레임워크 없이 비동기 쿼리를 작성할 수 있는 기본 제공 언어 기능을 제공합니다.

  • Room은 관찰 가능한 쿼리를 작성하기 위해 Kotlin's Flow 를 직접 지원합니다.
  • Room은 suspend 키워드를 요구하여 원샷 DAO 쿼리를 Kotlin 코루틴으로 비동기식으로 만듭니다.

코루틴 및 Flow 지원은 핵심 Room 런타임에 직접 빌드되므로 추가 아티팩트가 필요하지 않습니다.

Kotlin 및 Java용 RxJava

Room 3.0은 RxJava 3 반환 유형을 지원합니다. RxJava 반환 유형을 사용하려면 데이터베이스 또는 DAO에서 RxJava 반환 유형 변환기를 등록해야 합니다.

  1. 빌드 구성에 androidx.room3:room3-rxjava3 아티팩트를 포함합니다.
  2. @Database 또는 @Dao 선언에 @DaoReturnTypeConverters(RxDaoReturnTypeConverters::class) 주석을 답니다.

Room은 다음 RxJava 3 반환 유형을 지원합니다.

LiveData 및 Guava

Room 3.0은 변환기를 사용하여 LiveData 및 Guava ListenableFuture 반환 유형을 지원합니다.

  • LiveData: androidx.room3:room3-livedata 아티팩트를 포함하고 데이터베이스 또는 DAO에 @DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class) 주석을 답니다.
  • Guava: androidx.room3:room3-guava 아티팩트를 포함하고 데이터베이스 또는 DAO에 @DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class) 주석을 답니다.

비동기 원샷 쿼리 작성

원샷 쿼리는 데이터베이스 작업으로, 한 번만 실행되며 실행 시점의 데이터 스냅샷을 가져옵니다. 다음은 비동기 원샷 쿼리를 보여주는 예입니다.

@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    suspend fun loadUserById(id: Int): User

    @Query("SELECT * from user WHERE region IN (:regions)")
    suspend fun loadUsersByRegion(regions: List<String>): List<User>
}

관찰 가능한 쿼리 작성

관찰 가능한 쿼리는 참조된 테이블이 변경될 때마다 새 값을 내보내는 읽기 작업입니다. 예를 들어 이 동작을 사용하여 데이터베이스가 변경될 때 표시된 항목 목록을 업데이트할 수 있습니다. 다음은 관찰 가능한 쿼리를 보여주는 예입니다.

@Dao
interface ObservableUserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    fun loadUserById(id: Int): Flow<User>

    @Query("SELECT * from user WHERE region IN (:regions)")
    fun loadUsersByRegion(regions: List<String>): Flow<List<User>>
}

데이터베이스 무효화 수동 추적

관찰 가능한 데이터베이스 작업을 수동으로 빌드해야 하는 경우 createFlow API를 사용할 수 있습니다.InvalidationTracker 이 API를 사용하면 특정 테이블의 수정을 추적하고 이러한 테이블이 변경될 때마다 알림을 내보내는 Flow를 만들 수 있습니다.

fun getArtistTours(db: RoomDatabase, from: Date, to: Date): Flow<Map<Artist, TourState>> {
    return db.invalidationTracker.createFlow("Artist").map { _ ->
        val artists = artistsDao.getAllArtists()
        val tours = tourService.fetchStates(artists.map { it.id })
        associateTours(artists, tours, from, to)
    }
}

기본적으로 반환된 Flow는 스트림을 시작하기 위해 등록된 모든 테이블이 포함된 초기 값을 내보냅니다. emitInitialState 매개변수를 false로 설정하여 이 동작을 사용 중지할 수 있습니다.

맞춤 DAO 반환 유형 변환기

Room 또는 확장 프로그램 라이브러리에서 직접 지원하지 않는 유형의 경우 맞춤 DAO 반환 유형 변환기를 정의하여 추가 반환 유형을 지원할 수 있습니다. DAO 함수의 결과를 맞춤 유형으로 변환하려면 변환기 함수에 @DaoReturnTypeConverter 주석을 답니다.

예를 들어 androidx.tracing을 사용하여 쿼리 실행 주위에 추적 섹션을 추가하는 변환기를 정의하여 실행을 맞춤 TracedQuery 유형으로 래핑하여 성능에 민감한 쿼리를 모니터링할 수 있습니다.

class TracedQuery<T>(val result: T)

object TracingDaoReturnTypeConverter {
    @DaoReturnTypeConverter([OperationType.READ])
    suspend fun <T> convert(
        rawQuery: RoomRawQuery,
        executeAndConvert: suspend () -> T
    ): TracedQuery<T> {
        val result = trace("TracedQuery: ${rawQuery.sql}") {
            executeAndConvert()
        }
        return TracedQuery(result)
    }
}

변환기를 사용하려면 데이터베이스 또는 DAO에 @DaoReturnTypeConverters 주석을 답니다.

@Dao
@DaoReturnTypeConverters(TracingDaoReturnTypeConverter::class)
interface MusicDao {
    @Query("SELECT * FROM Song")
    suspend fun getAllSongs(): TracedQuery<List<Song>>
}

DAO 반환 유형 변환기 초기화 제어

Room은 일반적으로 DAO 반환 유형 변환기의 인스턴스화를 처리합니다. 그러나 변환기 클래스에 추가 종속 항목을 전달해야 하는 경우 앱에서 초기화를 직접 제어해야 합니다. 이 경우 변환기 클래스에 @ProvidedDaoReturnTypeConverter 주석을 답니다.

@ProvidedDaoReturnTypeConverter
class TracingDaoReturnTypeConverter(val tracer: Tracer) {
    @DaoReturnTypeConverter([OperationType.READ])
    suspend fun <T> convert(
        rawQuery: RoomRawQuery,
        executeAndConvert: suspend () -> T
    ): TracedQuery<T> {
        val result = tracer.trace("TracedQuery: ${rawQuery.sql}") {
            executeAndConvert()
        }
        return TracedQuery(result)
    }
}

그런 다음 @DaoReturnTypeConverters에서 변환기 클래스를 선언하는 것 외에도 RoomDatabase.Builder.addDaoReturnTypeConverter 함수를 사용하여 변환기 클래스 인스턴스를 RoomDatabase 빌더에 전달합니다.

val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name")
    .addDaoReturnTypeConverter(TracingDaoReturnTypeConverter(myLoggerInstance))
    .build()

변환기 함수 요구사항

@DaoReturnTypeConverter 함수는 다음과 같은 여러 요구사항을 충족해야 합니다.

  • 일반적으로 executeAndConvert라는 기능 매개변수를 마지막 인수로 포함해야 합니다. 이 매개변수는 Room에서 쿼리를 실행하고 결과를 파싱하기 위해 생성하는 suspend 람다입니다.
    • 변환기에서 페이징과 같은 쿼리를 변환해야 하는 경우 람다는 RoomRawQuery 매개변수를 사용할 수 있습니다.
  • 선택적으로 람다 앞에 다음 매개변수를 허용할 수 있습니다.
    • db: RoomDatabase: 코루틴 범위를 가져오거나 추가 작업을 실행하는 데 유용한 데이터베이스 인스턴스에 액세스합니다.
    • tableNames: Array<String> 또는 List<String>: 관찰 가능한 유형에 유용한 쿼리에서 액세스하는 테이블의 이름을 제공합니다.
    • rawQuery: RoomRawQuery: 쿼리의 런타임 인스턴스를 제공합니다.
    • inTransaction: Boolean: 쿼리가 트랜잭션 내에서 실행되는지 여부를 나타냅니다.