Thư viện lưu trữ Room cung cấp một tầng trừu tượng qua SQLite để cho phép để truy cập cơ sở dữ liệu mạnh mẽ hơn trong khi vẫn khai thác toàn bộ sức mạnh của SQLite. Chiến dịch này tập trung vào việc sử dụng Room trong các dự án Kotlin Multiplatform (KMP). Để biết thêm thông tin về cách sử dụng Room, hãy xem bài viết Lưu dữ liệu trong cơ sở dữ liệu cục bộ bằng Room hoặc mẫu chính thức của chúng tôi.
Thiết lập phần phụ thuộc
Phiên bản Room hiện tại hỗ trợ KMP là 2.7.0-alpha01 trở lên.
Để thiết lập Room trong dự án KMP, hãy thêm các phần phụ thuộc cho cấu phần phần mềm trong
Tệp build.gradle.kts
cho mô-đun của bạn:
androidx.room:room-gradle-plugin
– Trình bổ trợ Gradle để định cấu hình giản đồ Roomandroidx.room:room-compiler
– Bộ xử lý KSP tạo mãandroidx.room:room-runtime
– Phần thời gian chạy của thư việnandroidx.sqlite:sqlite-bundled
– (Không bắt buộc) Thư viện SQLite đi kèm
Ngoài ra, bạn cần định cấu hình trình điều khiển SQLite của Room. Những trình điều khiển này khác nhau dựa trên nền tảng mục tiêu. Xem Triển khai trình điều khiển để biết nội dung mô tả về các cách triển khai trình điều khiển hiện có.
Để biết thêm thông tin về cách thiết lập, hãy xem phần dưới đây:
- Đặt vị trí giản đồ bằng trình bổ trợ Room cho Gradle.
- KSP với Kotlin Multiplatform.
- Thêm các phần phụ thuộc thời gian chạy.
Xác định các lớp cơ sở dữ liệu
Bạn cần tạo một lớp cơ sở dữ liệu được chú thích bằng @Database
cùng với các DAO
và các thực thể bên trong nhóm tài nguyên chung của mô-đun KMP dùng chung. Đặt hàng
các lớp này trong các nguồn chung sẽ cho phép chia sẻ chúng trên tất cả các nhóm
nền tảng.
Khi bạn khai báo một đối tượng expect
bằng giao diện
RoomDatabaseConstructor
, trình biên dịch Room sẽ tạo actual
thực tế. Android Studio có thể đưa ra cảnh báo
"Expected object 'AppDatabaseConstructor' has no actual declaration in
module"
; bạn có thể loại bỏ cảnh báo bằng @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>
@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
)
Xin lưu ý rằng bạn có thể tuỳ ý sử dụng nội dung tuyên bố thực tế / dự kiến
để tạo hoạt động triển khai Room dành riêng cho từng nền tảng. Ví dụ: bạn có thể thêm
DAO dành riêng cho nền tảng được xác định trong mã chung bằng cách sử dụng expect
, sau đó
chỉ định các định nghĩa actual
cùng với các truy vấn bổ sung theo nền tảng cụ thể
nhóm tài nguyên.
Tạo trình tạo cơ sở dữ liệu
Bạn cần xác định một trình tạo cơ sở dữ liệu để tạo thực thể cho Room trên từng nền tảng. Chiến dịch này
là phần duy nhất của API bắt buộc phải ở trong nguồn dành riêng cho từng nền tảng
do sự khác biệt về API hệ thống tệp. Ví dụ: trong Android,
thông tin vị trí cơ sở dữ liệu thường được lấy thông qua
Context.getDatabasePath()
API, còn đối với iOS, vị trí cơ sở dữ liệu là
lấy bằng NSFileManager
.
Android
Để tạo phiên bản thể hiện của cơ sở dữ liệu, hãy chỉ định Ngữ cảnh cùng với cơ sở dữ liệu đường dẫn.
// 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
Để tạo phiên bản thể hiện của cơ sở dữ liệu, hãy cung cấp đường dẫn cơ sở dữ liệu bằng cách sử dụng tham số
NSFileManager
, thường nằm trong NSDocumentDirectory
.
// 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 (Máy tính)
Để tạo phiên bản thể hiện của cơ sở dữ liệu, hãy cung cấp một đường dẫn cơ sở dữ liệu bằng Java hoặc 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,
)
}
Tạo thực thể cơ sở dữ liệu
Sau khi bạn nhận được RoomDatabase.Builder
từ một trong các nền tảng cụ thể
hàm khởi tạo, bạn có thể định cấu hình phần còn lại của cơ sở dữ liệu Room trong mã chung
cùng với việc tạo thực thể cơ sở dữ liệu thực tế.
// shared/src/commonMain/kotlin/Database.kt
fun getRoomDatabase(
builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
return builder
.addMigrations(MIGRATIONS)
.fallbackToDestructiveMigrationOnDowngrade()
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
Chọn SQLiteDriver
Các đoạn mã trước đó sử dụng BundledSQLiteDriver
. Đây là
trình điều khiển được đề xuất bao gồm SQLite được biên dịch từ nguồn, cung cấp
phiên bản SQLite nhất quán và mới nhất trên tất cả các nền tảng. Nếu bạn
nếu muốn sử dụng SQLite do hệ điều hành cung cấp, hãy sử dụng API setDriver
theo từng nền tảng cụ thể
các nhóm tài nguyên chỉ định trình điều khiển dành riêng cho nền tảng. Đối với Android, bạn có thể sử dụng
AndroidSQLiteDriver
, còn đối với iOS, bạn có thể dùng NativeSQLiteDriver
. Người nhận
sử dụng NativeSQLiteDriver
, bạn cần cung cấp một tuỳ chọn trình liên kết để hệ điều hành iOS
ứng dụng tự động liên kết với SQLite của hệ thống.
// 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")
}
}
}
Điểm khác biệt
Room ban đầu được phát triển dưới dạng một thư viện Android nhưng sau đó được di chuyển sang KMP tập trung vào khả năng tương thích API. Phiên bản KMP của Room có sự khác biệt giữa các nền tảng và từ phiên bản dành riêng cho Android. Những điểm khác biệt này được liệt kê và mô tả như sau.
Chặn các hàm DAO
Khi sử dụng Room dành cho KMP, tất cả các hàm DAO được biên dịch cho các nền tảng không phải Android
cần phải là hàm suspend
, ngoại trừ các loại dữ liệu trả về phản ứng, chẳng hạn như
dưới tên Flow
.
// 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() { // … }
}
Các lợi ích của Room khi sử dụng thư viện kotlinx.coroutines
không đồng bộ với nhiều tính năng
mà Kotlin cung cấp cho nhiều nền tảng. Để có chức năng tối ưu, suspend
được thực thi cho các DAO được biên dịch trong một dự án KMP, ngoại trừ
Các DAO dành riêng cho Android để duy trì khả năng tương thích ngược với
cơ sở mã.
Sự khác biệt về tính năng với KMP
Phần này mô tả sự khác biệt giữa các tính năng giữa KMP và nền tảng Android các phiên bản của Room.
Hàm @RawQuery DAO
Các hàm được chú giải bằng @RawQuery
được biên dịch cho các nền tảng không phải Android
sẽ cần khai báo một tham số thuộc loại RoomRawQuery
thay vì
SupportSQLiteQuery
.
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query RoomRawQuery): List<TodoEntity>
}
Sau đó, RoomRawQuery
có thể được dùng để tạo truy vấn trong thời gian chạy:
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)
}
Lệnh gọi lại truy vấn
Các API sau đây để định cấu hình lệnh gọi lại truy vấn không có chung và do đó không khả dụng trong các nền tảng khác ngoài Android.
RoomDatabase.Builder.setQueryCallback
RoomDatabase.QueryCallback
Chúng tôi dự định sẽ hỗ trợ lệnh gọi lại truy vấn trong phiên bản Room sau này.
API để định cấu hình RoomDatabase
bằng lệnh gọi lại truy vấn
RoomDatabase.Builder.setQueryCallback
cùng với giao diện gọi lại
RoomDatabase.QueryCallback
không có chung nên không có sẵn
trên các nền tảng khác ngoài Android.
Tự động đóng cơ sở dữ liệu
API để bật tính năng tự động đóng sau khi hết thời gian chờ,
RoomDatabase.Builder.setAutoCloseTimeout
, chỉ có trên Android và
không có ở các nền tảng khác.
Cơ sở dữ liệu đóng gói sẵn
Các API sau đây để tạo RoomDatabase
bằng cơ sở dữ liệu hiện có (ví dụ:
cơ sở dữ liệu đóng gói sẵn) không có chung nên không có trong
các nền tảng khác ngoài Android. Các API đó là:
RoomDatabase.Builder.createFromAsset
RoomDatabase.Builder.createFromFile
RoomDatabase.Builder.createFromInputStream
RoomDatabase.PrepackagedDatabaseCallback
Chúng tôi dự định thêm tính năng hỗ trợ cho cơ sở dữ liệu đóng gói sẵn trong phiên bản tương lai của Phòng.
Vô hiệu hoá nhiều thực thể
API để bật tính năng vô hiệu hoá nhiều phiên bản,
RoomDatabase.Builder.enableMultiInstanceInvalidation
chỉ có trên
Android và không dùng được trên các nền tảng phổ biến hoặc nền tảng khác.