Room 지속성 라이브러리는 SQLite에 추상화 계층을 제공하여 SQLite를 완벽히 활용하면서 더 견고한 데이터베이스 액세스를 가능하게 합니다. 이 페이지에서는 Kotlin 멀티플랫폼 (KMP) 프로젝트에서 Room을 사용하는 방법을 중점적으로 설명합니다. Room 사용에 관한 자세한 내용은 Room을 사용하여 로컬 데이터베이스에 데이터 저장 또는 공식 샘플을 참고하세요.
종속 항목 설정
KMP를 지원하는 Room의 현재 버전은 2.7.0-alpha01 이상입니다.
KMP 프로젝트에서 Room을 설정하려면 모듈의 build.gradle.kts
파일에 아티팩트의 종속 항목을 추가합니다.
androidx.room:room-gradle-plugin
- Room 스키마를 구성하는 Gradle 플러그인androidx.room:room-compiler
- 코드를 생성하는 KSP 프로세서androidx.room:room-runtime
- 라이브러리의 런타임 부분androidx.sqlite:sqlite-bundled
- (선택사항) 번들로 제공된 SQLite 라이브러리
또한 Room의 SQLite 드라이버를 구성해야 합니다. 이러한 드라이버는 대상 플랫폼에 따라 다릅니다. 사용 가능한 드라이버 구현에 관한 설명은 드라이버 구현을 참고하세요.
추가 설정 정보는 다음을 참고하세요.
데이터베이스 클래스 정의
공유 KMP 모듈의 공통 소스 세트 내에 DAO 및 항목과 함께 @Database
주석이 달린 데이터베이스 클래스를 만들어야 합니다. 이러한 클래스를 공통 소스에 배치하면 모든 대상 플랫폼에서 공유할 수 있습니다.
RoomDatabaseConstructor
인터페이스로 expect
객체를 선언하면 Room 컴파일러가 actual
구현을 생성합니다. Android 스튜디오에서 "Expected object 'AppDatabaseConstructor' has no actual declaration in
module"
경고를 표시할 수 있습니다. @Suppress("NO_ACTUAL_FOR_EXPECT")
를 사용하여 경고를 표시하지 않을 수 있습니다.
// shared/src/commonMain/kotlin/Database.kt
@Database(entities = [TodoEntity::class], version = 1)
@ConstructedBy(AppDatabaseConstructor::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun getDao(): TodoDao
}
// The Room compiler generates the `actual` implementations.
@Suppress("NO_ACTUAL_FOR_EXPECT")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
override fun initialize(): AppDatabase
}
@Dao
interface TodoDao {
@Insert
suspend fun insert(item: TodoEntity)
@Query("SELECT count(*) FROM TodoEntity")
suspend fun count(): Int
@Query("SELECT * FROM TodoEntity")
fun getAllAsFlow(): Flow<List<TodoEntity>>
}
@Entity
data class TodoEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String
)
원하는 경우 actual / expect 선언을 사용하여 플랫폼별 Room 구현을 만들 수 있습니다. 예를 들어 expect
를 사용하여 공통 코드에 정의된 플랫폼별 DAO를 추가한 다음 플랫폼별 소스 세트에서 추가 쿼리로 actual
정의를 지정할 수 있습니다.
데이터베이스 빌더 만들기
각 플랫폼에서 Room을 인스턴스화할 데이터베이스 빌더를 정의해야 합니다. 이는 파일 시스템 API의 차이로 인해 플랫폼별 소스 세트에 있어야 하는 API의 유일한 부분입니다. 예를 들어 Android에서는 일반적으로 Context.getDatabasePath()
API를 통해 데이터베이스 위치를 가져오지만 iOS에서는 NSFileManager
를 사용하여 데이터베이스 위치를 가져옵니다.
Android
데이터베이스 인스턴스를 만들려면 데이터베이스 경로와 함께 컨텍스트를 지정합니다.
// shared/src/androidMain/kotlin/Database.kt
fun getDatabaseBuilder(ctx: Context): RoomDatabase.Builder<AppDatabase> {
val appContext = ctx.applicationContext
val dbFile = appContext.getDatabasePath("my_room.db")
return Room.databaseBuilder<AppDatabase>(
context = appContext,
name = dbFile.absolutePath
)
}
iOS
데이터베이스 인스턴스를 만들려면 일반적으로 NSDocumentDirectory
에 있는 NSFileManager
를 사용하여 데이터베이스 경로를 제공합니다.
// shared/src/iosMain/kotlin/Database.kt
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFilePath = documentDirectory() + "/my_room.db"
return Room.databaseBuilder<AppDatabase>(
name = dbFilePath,
)
}
private fun documentDirectory(): String {
val documentDirectory = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
return requireNotNull(documentDirectory?.path)
}
JVM (데스크톱)
데이터베이스 인스턴스를 만들려면 Java 또는 Kotlin API를 사용하여 데이터베이스 경로를 제공합니다.
// shared/src/jvmMain/kotlin/Database.kt
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFile = File(System.getProperty("java.io.tmpdir"), "my_room.db")
return Room.databaseBuilder<AppDatabase>(
name = dbFile.absolutePath,
)
}
축소 및 난독화
프로젝트가 축소되거나 난독화된 경우 Room에서 생성된 데이터베이스 정의 구현을 찾을 수 있도록 다음 Proguard 규칙을 포함해야 합니다.
-keep class * extends androidx.room.RoomDatabase { <init>(); }
데이터베이스 인스턴스화
플랫폼별 생성자 중 하나에서 RoomDatabase.Builder
를 가져오면 실제 데이터베이스 인스턴스화와 함께 공통 코드에서 나머지 Room 데이터베이스를 구성할 수 있습니다.
// shared/src/commonMain/kotlin/Database.kt
fun getRoomDatabase(
builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
return builder
.addMigrations(MIGRATIONS)
.fallbackToDestructiveMigrationOnDowngrade()
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
SQLiteDriver 선택
이전 코드 스니펫은 BundledSQLiteDriver
를 사용합니다. 소스에서 컴파일된 SQLite를 포함하는 권장 드라이버로, 모든 플랫폼에서 가장 일관되고 최신 버전의 SQLite를 제공합니다. OS 제공 SQLite를 사용하려면 플랫폼별 드라이버를 지정하는 플랫폼별 소스 세트에서 setDriver
API를 사용하세요. Android의 경우 AndroidSQLiteDriver
를 사용하고 iOS의 경우 NativeSQLiteDriver
를 사용할 수 있습니다. NativeSQLiteDriver
를 사용하려면 iOS 앱이 시스템 SQLite와 동적으로 연결되도록 링커 옵션을 제공해야 합니다.
// shared/build.gradle.kts
kotlin {
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "TodoApp"
isStatic = true
// Required when using NativeSQLiteDriver
linkerOpts.add("-lsqlite3")
}
}
}
차이점
Room은 원래 Android 라이브러리로 개발되었으며 나중에 API 호환성에 중점을 두고 KMP로 이전되었습니다. Room의 KMP 버전은 플랫폼마다 다르며 Android 전용 버전과도 다릅니다. 이러한 차이점은 다음과 같이 나열되고 설명됩니다.
차단 DAO 함수
KMP용 Room을 사용하는 경우 Android 이외의 플랫폼용으로 컴파일된 모든 DAO 함수는 Flow
와 같은 반응형 반환 유형을 제외하고 suspend
함수여야 합니다.
// shared/src/commonMain/kotlin/MultiplatformDao.kt
@Dao
interface MultiplatformDao {
// ERROR: Blocking function not valid for non-Android targets
@Query("SELECT * FROM Entity")
fun blockingQuery(): List<Entity>
// OK
@Query("SELECT * FROM Entity")
suspend fun query(): List<Entity>
// OK
@Query("SELECT * FROM Entity")
fun queryFlow(): Flow<List<Entity>>
// ERROR: Blocking function not valid for non-Android targets
@Transaction
fun blockingTransaction() { // … }
// OK
@Transaction
suspend fun transaction() { // … }
}
Room은 Kotlin이 여러 플랫폼에 제공하는 기능이 풍부한 비동기 kotlinx.coroutines
라이브러리의 이점을 누립니다. 최적의 기능을 위해 기존 코드베이스와의 하위 호환성을 유지하기 위한 Android 전용 DAO를 제외하고 KMP 프로젝트에서 컴파일된 DAO에 suspend
함수가 적용됩니다.
KMP와의 기능 차이
이 섹션에서는 Room의 KMP 버전과 Android 플랫폼 버전 간의 기능 차이를 설명합니다.
@RawQuery DAO 함수
Android 이외의 플랫폼용으로 컴파일된 @RawQuery
로 주석 처리된 함수는 SupportSQLiteQuery
대신 RoomRawQuery
유형의 매개변수를 선언해야 합니다.
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query RoomRawQuery): List<TodoEntity>
}
그런 다음 RoomRawQuery
를 사용하여 런타임에 쿼리를 만들 수 있습니다.
suspend fun getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = RoomRawQuery(
sql = "SELECT * FROM TodoEntity WHERE title = ?"
onBindStatement = {
it.bindText(1, title.lowercase())
}
)
return todosDao.getTodos(query)
}
쿼리 콜백
쿼리 콜백을 구성하기 위한 다음 API는 일반적으로 사용할 수 없으므로 Android 이외의 플랫폼에서는 사용할 수 없습니다.
RoomDatabase.Builder.setQueryCallback
RoomDatabase.QueryCallback
향후 Room 버전에서 쿼리 콜백 지원을 추가할 예정입니다.
쿼리 콜백 RoomDatabase.Builder.setQueryCallback
및 콜백 인터페이스 RoomDatabase.QueryCallback
를 사용하여 RoomDatabase
를 구성하는 API는 일반적으로 사용할 수 없으므로 Android 이외의 다른 플랫폼에서는 사용할 수 없습니다.
데이터베이스 자동 종료
시간 초과 후 자동 닫기를 사용 설정하는 API인 RoomDatabase.Builder.setAutoCloseTimeout
는 Android에서만 사용할 수 있으며 다른 플랫폼에서는 사용할 수 없습니다.
사전 패키징된 데이터베이스
기존 데이터베이스 (예: 사전 패키징된 데이터베이스)를 사용하여 RoomDatabase
를 만드는 다음 API는 일반적으로 사용할 수 없으므로 Android 이외의 다른 플랫폼에서는 사용할 수 없습니다. 이러한 API는 다음과 같습니다.
RoomDatabase.Builder.createFromAsset
RoomDatabase.Builder.createFromFile
RoomDatabase.Builder.createFromInputStream
RoomDatabase.PrepackagedDatabaseCallback
향후 Room 버전에서는 사전 패키징된 데이터베이스를 지원할 예정입니다.
다중 인스턴스 무효화
다중 인스턴스 무효화를 사용 설정하는 API인 RoomDatabase.Builder.enableMultiInstanceInvalidation
는 Android에서만 사용할 수 있으며 일반적인 플랫폼이나 다른 플랫폼에서는 사용할 수 없습니다.