Lưu dữ liệu trong cơ sở dữ liệu cục bộ bằng Room   Một phần của Android Jetpack.

Dùng thử với Kotlin Multiplatform
Kotlin Multiplatform cho phép chia sẻ lớp cơ sở dữ liệu với các nền tảng khác. Tìm hiểu cách thiết lập và làm việc với Cơ sở dữ liệu Room trong KMP

Những ứng dụng xử lý lượng dữ liệu có cấu trúc với lượng không nhỏ có thể được hưởng nhiều lợi ích khi lưu dữ liệu đó trên máy. Trường hợp sử dụng phổ biến nhất là lưu các phần dữ liệu có liên quan vào bộ nhớ đệm. Bằng cách này, khi thiết bị không thể truy cập mạng, người dùng vẫn duyệt qua được nội dung đó.

Thư viện lưu trữ Room cung cấp một lớp trừu tượng qua SQLite để mang lại khả năng truy cập cơ sở dữ liệu dễ dàng, đồng thời khai thác toàn bộ sức mạnh của SQLite. Cụ thể, Room đem lại các lợi ích sau:

  • Xác minh thời gian biên dịch của truy vấn SQL.
  • Chú thích tiện lợi giúp giảm thiểu mã nguyên mẫu lặp lại, dễ mắc lỗi.
  • Hợp lý hoá đường dẫn di chuyển cơ sở dữ liệu.

Bạn nên dùng Room thay vì sử dụng trực tiếp API SQLite.

Thiết lập

Để dùng Room trong ứng dụng, hãy thêm các phần phụ thuộc sau vào tệp build.gradle.kts của mô-đun. Room 3.0 yêu cầu KSP để xử lý chú thích.

Kotlin

dependencies {
    val room_version = "3.0.0"

    implementation("androidx.room3:room3-runtime:$room_version")
    ksp("androidx.room3:room3-compiler:$room_version")
}

Groovy

dependencies {
    def room_version = "3.0.0"

    implementation "androidx.room3:room3-runtime:$room_version"

    ksp "androidx.room3:room3-compiler:$room_version"
}

Thành phần chính

Có 3 thành phần chính trong Room:

  • Lớp cơ sở dữ liệu lưu giữ cơ sở dữ liệu và đóng vai trò là điểm truy cập chính cho đường kết nối cơ bản đến dữ liệu cố định của ứng dụng.
  • Thực thể dữ liệu đại diện cho các bảng trong cơ sở dữ liệu của ứng dụng.
  • Đối tượng truy cập dữ liệu (DAO) cung cấp các hàm mà ứng dụng của bạn có thể dùng để truy vấn, cập nhật, chèn và xoá dữ liệu trong cơ sở dữ liệu.

Lớp cơ sở dữ liệu cung cấp cho ứng dụng của bạn các thực thể của DAO được liên kết với cơ sở dữ liệu đó. Đổi lại, ứng dụng có thể dùng DAO để truy xuất dữ liệu từ cơ sở dữ liệu dưới dạng thực thể của đối tượng thực thể dữ liệu được liên kết. Ứng dụng cũng có thể dùng các thực thể dữ liệu đã xác định để cập nhật các hàng trong bảng tương ứng hoặc tạo hàng mới để chèn dữ liệu. Hình 1 minh hoạ mối quan hệ giữa các thành phần của Room.

Hình 1. Sơ đồ cấu trúc thư viện Room.

Triển khai mẫu

Phần này trình bày phương thức triển khai mẫu cho cơ sở dữ liệu Room với một thực thể dữ liệu và một DAO duy nhất.

Thực thể dữ liệu

Mã sau đây định nghĩa một thực thể dữ liệu User. Mỗi thực thể của User biểu thị một hàng trong bảng user thuộc cơ sở dữ liệu của ứng dụng.

@Entity
data class User(
    @PrimaryKey val uid: Int,
    @ColumnInfo(name = "first_name") val firstName: String,
    @ColumnInfo(name = "last_name") val lastName: String
)

Để tìm hiểu thêm về các thực thể dữ liệu trong Room, hãy xem bài viết Xác định dữ liệu bằng các thực thể của Room.

Đối tượng truy cập dữ liệu (DAO)

Đoạn mã sau đây định nghĩa một DAO có tên UserDao. UserDao cung cấp các hàm mà phần còn lại của ứng dụng sẽ dùng để tương tác với dữ liệu trong bảng user.

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    suspend fun getAll(): List<User>

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    suspend fun loadAllByIds(userIds: IntArray): List<User>

    @Query(
        """
        SELECT * FROM user
        WHERE first_name LIKE :first AND last_name LIKE :last LIMIT 1
        """
    )
    suspend fun findByName(first: String, last: String): User

    @Insert
    suspend fun insertAll(vararg users: User)

    @Delete
    suspend fun delete(user: User)
}

Để tìm hiểu thêm về DAO, hãy xem bài viết Truy cập vào dữ liệu bằng các DAO của Room.

Cơ sở dữ liệu

Đoạn mã sau đây định nghĩa một lớp AppDatabase để lưu giữ cơ sở dữ liệu. AppDatabase định nghĩa cấu hình cơ sở dữ liệu và đóng vai trò là điểm truy cập chính của ứng dụng vào dữ liệu cố định. Lớp cơ sở dữ liệu phải đáp ứng các điều kiện sau:

  • Lớp phải được chú thích bằng thẻ chú thích @Database có chứa mảng (array) entities liệt kê mọi thực thể dữ liệu được liên kết với cơ sở dữ liệu.
  • Lớp này phải là một lớp trừu tượng mở rộng RoomDatabase.
  • Đối với mỗi lớp DAO được liên kết với cơ sở dữ liệu, lớp cơ sở dữ liệu phải xác định một hàm trừu tượng không có đối số và trả về một thực thể của lớp DAO.

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

Lưu ý: Nếu ứng dụng của bạn chạy trong một quy trình, thì bạn nên tuân theo mẫu thiết kế singleton khi tạo thực thể cho đối tượng AppDatabase. Mỗi thực thể RoomDatabase đều khá tốn kém và bạn hiếm khi cần quyền truy cập vào nhiều thực thể trong một quy trình.

Nếu ứng dụng của bạn chạy trong nhiều quy trình, hãy đưa enableMultiInstanceInvalidation() vào lệnh gọi trình tạo cơ sở dữ liệu. Bằng cách đó, khi có thực thể của AppDatabase trong mỗi quy trình, bạn có thể vô hiệu hoá tệp cơ sở dữ liệu dùng chung trong một quy trình và hoạt động vô hiệu hoá này sẽ tự động truyền đến các thực thể của AppDatabase trong những quy trình khác.

Cách sử dụng

Sau khi định nghĩa thực thể dữ liệu, DAO và đối tượng cơ sở dữ liệu, bạn có thể dùng đoạn mã sau để tạo một thực thể của cơ sở dữ liệu:

val db =
    Room.databaseBuilder<AppDatabase>(applicationContext, "database-name")
        .setDriver(AndroidSQLiteDriver())
        .build()

Sau đó, bạn có thể dùng các hàm trừu tượng từ AppDatabase để lấy một thực thể của DAO. Đổi lại, bạn có thể dùng các hàm từ thực thể DAO để tương tác với cơ sở dữ liệu:

val userDao = db.userDao()
val users: List<User> = userDao.getAll()

Tài nguyên khác

Để tìm hiểu thêm về Room, hãy xem thêm các tài nguyên sau đây:

Mẫu