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 없음:
SQLiteDriverAPI가 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를 계속 사용하는 동안 이 전환을 실행할 수 있습니다.
모듈의
build.gradle.kts에서 KSP 플러그인을 적용합니다.plugins { id("com.google.devtools.ksp") version "<ksp_version>" }KSP 버전이 Kotlin 버전과 호환되는지 확인합니다.
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가 모두 작동하는 호환성 모드로 작동합니다. 이 호환성 모드를 사용하면 드라이버를 사용 설정하기 전에 코드베이스를 점진적으로 변환할 수 있습니다.
- 이전 변환:
SupportSQLiteDatabase대신SQLiteConnection을 사용하도록Migration및AutoMigrationSpec하위 클래스를 이전합니다.
// 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) {
// ...
}
}
@RawQueryDAO 함수 변환:@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블록을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):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()
}