Room 2.x에서 Room 3.0으로 이전

Room 3.0은 라이브러리를 Kotlin 우선으로 전환하는 메이저 버전 업데이트입니다. Kotlin Multiplatform (KMP)을 지원하고 Kotlin Symbol Processing (KSP)이 필요하며 비동기 작업에 코루틴을 적용합니다.

기존 Room 2.x 앱 및 전이 종속 항목과의 호환성 문제를 방지하기 위해 Room 3.0은 새 패키지인 androidx.room3에 있습니다.

이 가이드에서는 기존 Room 2.x 구현을 Room 3.0으로 이전하는 데 필요한 단계를 설명합니다.

Room 3.0의 주요 변경사항

이전을 시작하기 전에 주요 차이점을 숙지하세요.

  • 새 패키지 및 아티팩트: 모든 클래스는 androidx.room3에 있습니다. 아티팩트는 room3 프리픽스를 사용합니다. 예: androidx.room3:room3-runtime.
  • Kotlin 및 KSP만: Room 3.0은 자바 코드 생성을 지원하지 않습니다. KAPT 또는 자바 주석 프로세서 대신 KSP를 사용하세요. Room 3.0은 여전히 자바 소스를 입력으로 지원합니다.
  • 코루틴 우선: DAO 함수는 관찰 가능한 유형을 제외하고 suspend 함수여야 합니다. CoroutineContext가 실행기를 대체합니다.
  • SupportSQLite 없음: SQLiteDriver API가 Room을 지원합니다. Room은 핵심 API에서 SupportSQLiteDatabase를 삭제합니다.
  • API 변경사항: 이전 및 데이터베이스 콜백은 SQLiteConnection 대신 SupportSQLiteDatabase을 사용합니다.
  • 반응형 유형 변환기: RxJava, LiveData, Guava, Paging 반환 유형에는 @DaoReturnTypeConverters를 등록해야 합니다.

두 가지 개별 단계로 이전하는 것이 좋습니다. 먼저 Room 2.x에서 코드베이스를 준비하고 현대화한 다음 Room 3.0으로 전환합니다.


Room 2.x에서 준비 및 현대화

Room 3.0으로 이전하기 전에 Room 2.8과 같은 현재 Room 2.x 출시로 업데이트하여 대부분의 현대화 작업을 실행할 수 있습니다. Room 2.8은 Kotlin Multiplatform(KMP)을 지원하며 Room 3.0에서 사용하는 여러 드라이버 API를 포함합니다.

Room 2.8 이상으로 업데이트

현재 Room 2.x 출시를 사용하도록 빌드 구성을 업데이트합니다.

[versions]
room2 = "2.8.4" # Use the current Room 2.8 version

[libraries]
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room2" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room2" }

KAPT에서 KSP로 이전

Room 3.0은 자바 주석 프로세서 또는 KAPT를 지원하지 않습니다. Kotlin Symbol Processing (KSP)을 사용해야 합니다. Room 2.x를 계속 사용하는 동안 이 전환을 실행할 수 있습니다.

  1. 모듈의 build.gradle.kts에서 KSP 플러그인을 적용합니다.

    plugins {
        id("com.google.devtools.ksp") version "<ksp_version>"
    }
    

    KSP 버전이 Kotlin 버전과 호환되는지 확인합니다.

  2. Room 컴파일러 종속 항목의 경우 kapt 또는 annotationProcessor를 ksp로 바꿉니다.

    dependencies {
        implementation(libs.androidx.room.runtime)
        ksp(libs.androidx.room.compiler)
    }
    

코루틴 채택

Room 3.0에는 비동기 작업에 코루틴이 필요합니다.

  • DAO 업데이트: Flow 또는 RxJava 유형과 같은 관찰 가능한 반응형 유형을 반환하지 않는 한 모든 DAO 함수는 suspend 함수여야 합니다.
// Before (Blocking)
@Dao
interface UserDao {
    @Query("SELECT * FROM User")
    fun getAll(): List<User>
}

// After (Suspend)
@Dao
interface UserDao {
    @Query("SELECT * FROM User")
    suspend fun getAll(): List<User>
}
  • 데이터베이스 작업을 실행하기 위해 맞춤 Executor로 RoomDatabase를 구성한 경우 빌더에서 setQueryCoroutineContext를 사용하여 CoroutineContext로 이전합니다.
Room.databaseBuilder<AppDatabase>(context, "db")
    .setQueryCoroutineContext(Dispatchers.IO)
    .build()

드라이버 API 채택 및 Support SQLite 방지

Room 3.0은 SQLiteDriver에서 완전히 지원되며 더 이상 핵심 API에서 SupportSQLiteDatabase를 지원하지 않습니다.

데이터베이스 빌더에서 SQLiteDriver를 설정하기 위해 setDriver를 호출하지 않으면 Room 2.8은 Support SQLite와 드라이버 API가 모두 작동하는 호환성 모드에서 작동합니다. 이 호환성 모드를 사용하면 드라이버를 사용 설정하기 전에 코드베이스를 점진적으로 변환할 수 있습니다.

  • 이전 변환: Migration 및 AutoMigrationSpec 하위 클래스를 이전하여 SupportSQLiteDatabase 대신 SQLiteConnection을 사용합니다.
// Before (SupportSQLiteDatabase)
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL")
    }
}

// After (SQLiteConnection - Room 2.8)
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.execSQL

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(connection: SQLiteConnection) {
        connection.execSQL(
            "ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL"
        )
    }
}
  • 데이터베이스 콜백 변환: SQLiteConnection을 사용하도록 RoomDatabase.Callback 구현을 업데이트합니다.
// Before (SupportSQLiteDatabase)
val callback = object : RoomDatabase.Callback() {
    override fun onCreate(db: SupportSQLiteDatabase) {
        // ...
    }
}

// After (SQLiteConnection - Room 2.8)
val callback = object : RoomDatabase.Callback() {
    override fun onCreate(connection: SQLiteConnection) {
        // ...
    }
}
  • @RawQuery DAO 함수 변환: @RawQuery로 주석 처리된 함수의 경우 SupportSQLiteQuery 대신 RoomRawQuery를 사용합니다.
// Before (SupportSQLiteQuery)
@Dao
interface UserDao {
    @RawQuery
    fun getUser(query: SupportSQLiteQuery): User
}

// After (RoomRawQuery)
@Dao
interface UserDao {
    @RawQuery
    suspend fun getUser(query: RoomRawQuery): User
}

런타임 시 RoomRawQuery를 구성할 수 있습니다.

val query = RoomRawQuery(
    sql = "SELECT * FROM User WHERE id = ?",
    onBindStatement = { statement ->
        statement.bindInt(1, userId)
    }
)
  • 트랜잭션 API 변환: Android 전용 withTransaction 및 runInTransaction 블록을 useWriterConnection 및 immediateTransaction으로 바꿉니다.
// Before (withTransaction)
db.withTransaction {
    // perform database operations
}

// After (useWriterConnection - Room 2.8)
import androidx.room.useWriterConnection
import androidx.room.immediateTransaction

db.useWriterConnection { connection ->
    connection.immediateTransaction {
        // perform database operations
    }
}
  • `SupportSQLiteDatabase` 직접 사용 방지SupportSQLiteDatabase : 여전히 SupportSQLiteDatabase가 필요하고 아직 이전할 수 없는 광범위한 기존 코드가 있는 경우 androidx.room:room-sqlite-wrapper 호환성 아티팩트를 사용합니다.
dependencies {
    implementation("androidx.room:room-sqlite-wrapper:$roomVersion")
}

그런 다음 getSupportWrapper를 사용하여 Room 데이터베이스 인스턴스에서 SupportSQLiteDatabase를 가져옵니다.

import androidx.room.support.getSupportWrapper

val legacyDb: SupportSQLiteDatabase = roomDatabase.getSupportWrapper()
  • SQLite 드라이버 설정: 모든 Room API 사용을 드라이버 API로 이전한 후 BundledSQLiteDriver 또는 AndroidSQLiteDriver와 같은 드라이버를 RoomDatabase 빌더에서 setDriver를 호출하여 구성합니다.
import androidx.sqlite.driver.bundled.BundledSQLiteDriver

val db = Room.databaseBuilder<AppDatabase>(context, "db")
    .setDriver(BundledSQLiteDriver())
    .build()

흐름 기반 무효화 추적 채택

Room 2.8에서는 InvalidationTracker.createFlow API를 도입합니다. 이 API를 사용하여 Room 2.x를 계속 사용하는 동안 기존 InvalidationTracker.Observer 구현에서 이전합니다. 이렇게 하면 Observer를 완전히 삭제하는 Room 3.0을 위해 코드베이스가 준비됩니다.

// Before (InvalidationTracker.Observer)
val observer = object : InvalidationTracker.Observer("User") {
    override fun onInvalidated(tables: Set<String>) {
        // reload user data
    }
}
db.invalidationTracker.addObserver(observer)

// After (createFlow - Room 2.8)
val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
    userDao.getAllUsers()
}

Room 3.0으로 이전

Room 2.x에서 애플리케이션을 현대화한 후 Room 3.0으로 전환하려면 종속 항목, 패키지 가져오기, 데이터베이스 콜백을 업데이트해야 합니다.

종속 항목 및 패키지 가져오기 업데이트

  • 빌드 구성에서 androidx.room 종속 항목을 androidx.room3으로 바꿉니다.
[versions]
room3 = "3.0.0" # Use the current Room 3.0 version

[libraries]
androidx-room3-runtime = { module = "androidx.room3:room3-runtime", version.ref = "room3" }
androidx-room3-compiler = { module = "androidx.room3:room3-compiler", version.ref = "room3" }
  • 종속 항목 블록을 업데이트합니다.
dependencies {
    implementation(libs.androidx.room3.runtime)
    ksp(libs.androidx.room3.compiler)
}
  • 패키지 가져오기를 업데이트합니다. import androidx.room.*을 import androidx.room3.*으로 바꿉니다.

유형 변환기 API 업데이트

Room 3.0은 열 값 변환에 관한 사용법을 명확히 하고 DAO 반환 유형 변환기와의 혼동을 방지하기 위해 유형 변환기 API의 이름을 바꿉니다.

코드베이스에서 다음 주석과 함수를 업데이트합니다.

  • @TypeConverter의 이름을 @ColumnTypeConverter로 바꿉니다.
  • @TypeConverters의 이름을 @ColumnTypeConverters로 바꿉니다.
  • @ProvidedTypeConverter의 이름을 @ProvidedColumnTypeConverter로 바꿉니다.
  • RoomDatabase.Builder.addTypeConverter의 이름을 addColumnTypeConverter로 바꿉니다.

예:

// Before
@ProvidedTypeConverter
class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? = ...
}

@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()

val db = Room.databaseBuilder<AppDatabase>(...)
  .addTypeConverter(convertersInstance)
  .build()
// After
import androidx.room3.ColumnTypeConverter
import androidx.room3.ColumnTypeConverters
import androidx.room3.ProvidedColumnTypeConverter

@ProvidedColumnTypeConverter
class Converters {
  @ColumnTypeConverter
  fun fromTimestamp(value: Long?): Date? = ...
}

@Database(entities = [User::class], version = 1)
@ColumnTypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()

val db = Room.databaseBuilder<AppDatabase>(...)
  .addColumnTypeConverter(convertersInstance)
  .build()

콜백을 정지 함수로 업데이트

Room 3.0에서 데이터베이스 콜백 및 이전은 SQLiteConnection을 사용하며 suspend 함수입니다.

  • 수동 Migration 클래스를 업데이트합니다.
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.async.executeSQL

val MIGRATION_1_2 = object : Migration(1, 2) {
    override suspend fun migrate(connection: SQLiteConnection) {
        connection.executeSQL(
            "ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL"
        )
    }
}
  • RoomDatabase.Callback 구현을 업데이트합니다.
val callback = object : RoomDatabase.Callback() {
    override suspend fun onCreate(connection: SQLiteConnection) {
        // ...
    }
}

DAO 반환 유형 변환기 등록

Room 3.0에서 RxJava, LiveData, Guava, Paging과 같은 반응형 반환 유형에는 @DaoReturnTypeConverters를 사용하여 DAO 반환 유형 변환기를 등록해야 합니다.

import androidx.room3.paging.PagingSourceDaoReturnTypeConverter

@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface UserDao {
    @Query("SELECT * FROM User")
    fun getAllPaginated(): PagingSource<Int, User>
}
  • Paging (PagingSource): PagingSourceDaoReturnTypeConverter 를 androidx.room3:room3-paging 아티팩트에서 등록합니다.
  • RxJava (Observable, Flowable, Single, Maybe, Completable): androidx.room3:room3-rxjava3 아티팩트에서 RxDaoReturnTypeConverters를 등록합니다.
  • Guava (ListenableFuture): androidx.room3:room3-guava 아티팩트에서 GuavaDaoReturnTypeConverter를 등록합니다.
  • LiveData (LiveData): androidx.room3:room3-livedata 아티팩트에서 LiveDataDaoReturnTypeConverter를 등록합니다.

InvalidationTracker Observer 삭제 확인

Room 3.0은 InvalidationTracker.Observer 및 addObserver, removeObserver와 같은 관련 등록 메서드를 완전히 삭제합니다.

1단계에서 코루틴 흐름으로 아직 전환하지 않은 경우 모든 Observer 사용을 createFlow로 이전해야 합니다.

val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
    userDao.getAllUsers()
}