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.

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.

Do những điểm cần lưu ý này, 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 của bạn, hãy thêm các phần phụ thuộc sau vào tệp build.gradle của ứng dụng:

Groovy

dependencies {
    def room_version = "2.5.0"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"

    // To use Kotlin annotation processing tool (kapt)
    kapt "androidx.room:room-compiler:$room_version"
    // To use Kotlin Symbol Processing (KSP)
    ksp "androidx.room:room-compiler:$room_version"

    // optional - RxJava2 support for Room
    implementation "androidx.room:room-rxjava2:$room_version"

    // optional - RxJava3 support for Room
    implementation "androidx.room:room-rxjava3:$room_version"

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "androidx.room:room-guava:$room_version"

    // optional - Test helpers
    testImplementation "androidx.room:room-testing:$room_version"

    // optional - Paging 3 Integration
    implementation "androidx.room:room-paging:$room_version"
}

Kotlin

dependencies {
    val room_version = "2.5.0"

    implementation("androidx.room:room-runtime:$room_version")
    annotationProcessor("androidx.room:room-compiler:$room_version")

    // To use Kotlin annotation processing tool (kapt)
    kapt("androidx.room:room-compiler:$room_version")
    // To use Kotlin Symbol Processing (KSP)
    ksp("androidx.room:room-compiler:$room_version")

    // optional - Kotlin Extensions and Coroutines support for Room
    implementation("androidx.room:room-ktx:$room_version")

    // optional - RxJava2 support for Room
    implementation("androidx.room:room-rxjava2:$room_version")

    // optional - RxJava3 support for Room
    implementation("androidx.room:room-rxjava3:$room_version")

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation("androidx.room:room-guava:$room_version")

    // optional - Test helpers
    testImplementation("androidx.room:room-testing:$room_version")

    // optional - Paging 3 Integration
    implementation("androidx.room:room-paging:$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 biểu thị 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 phương thức 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.

Kotlin

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

Java

@Entity
public class User {
    @PrimaryKey
    public int uid;

    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    public String lastName;
}

Để 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 phương thức 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.

Kotlin

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

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

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

    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)
}

Java

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

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

    @Insert
    void insertAll(User... users);

    @Delete
    void 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 định nghĩa một phương thức trừu tượng không có đối số và trả về một thực thể của lớp DAO.

Kotlin

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

Java

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract 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:

Kotlin

val db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java, "database-name"
        ).build()

Java

AppDatabase db = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "database-name").build();

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

Kotlin

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

Java

UserDao userDao = db.userDao();
List<User> users = 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

Lớp học lập trình

Blog