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 또는 annotationProcessorksp로 바꿉니다.

    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>
}
  • 데이터베이스 작업을 실행하기 위해 커스텀 ExecutorRoomDatabase를 구성한 경우 빌더에서 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가 모두 작동하는 호환성 모드로 작동합니다. 이 호환성 모드를 사용하면 드라이버를 사용 설정하기 전에 코드베이스를 점진적으로 변환할 수 있습니다.

  • 이전 변환: SupportSQLiteDatabase 대신 SQLiteConnection을 사용하도록 MigrationAutoMigrationSpec 하위 클래스를 이전합니다.
// 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 전용 withTransactionrunInTransaction 블록을 withWriteTransaction 또는 withReadTransaction으로 바꿉니다.
// Before (withTransaction)
db.withTransaction {
    // perform database operations
}

// After (withWriteTransaction - Room 2.8)
import androidx.room.withWriteTransaction

db.withWriteTransaction {
    // perform database operations
}

트랜잭션 연결에 직접 로우 레벨 액세스가 필요한 경우 immediateTransaction과 함께 useWriterConnection을 사용할 수도 있습니다.

  • 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): PagingSourceDaoReturnTypeConverterandroidx.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.ObserveraddObserver, removeObserver와 같은 관련 등록 메서드를 완전히 삭제합니다.

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

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