Room: các khái niệm chính, Entity, DAO và làm việc với cơ sở dữ liệu

Tác giả: IT Sectr Đã đăng: 2026-05-04 Thời gian đọc: 8 phút

Room là một thư viện để làm việc với SQLite trong Android, một phần của Jetpack. Nó cung cấp một lớp trừu tượng trên SQLite thô, tự động hóa việc tạo bảng, thực thi truy vấn và chuyển đổi dữ liệu thành các đối tượng Kotlin và Java. Theo Android Developers, Room biên dịch các truy vấn SQL tại thời điểm xây dựng, kiểm tra tính đúng đắn của cú pháp và mối quan hệ giữa Entity và các bảng.

Những điểm chính

  • Room là thư viện ORM của Jetpack để làm việc với SQLite trong các ứng dụng Android.
  • Entity là một lớp được chú thích với @Entity, mỗi thể hiện tương ứng với một hàng trong bảng.
  • DAO là một đối tượng truy cập dữ liệu với các phương thức được chú thích cho truy vấn SQL.
  • Database là một lớp trừu tượng mở rộng RoomDatabase, liên kết Entity và DAO.
  • Di chuyển là cơ chế thay đổi schema cơ sở dữ liệu một cách an toàn mà không làm mất dữ liệu người dùng.

Room là gì và tại sao cần nó

Room là một thư viện lưu trữ của Android Jetpack cung cấp ánh xạ đối tượng-quan hệ cho SQLite. Room giải quyết ba vấn đề chính của SQLite thô: viết lượng lớn mã boilerplate để tạo bảng, thiếu kiểm tra truy vấn SQL tại thời điểm biên dịch và chuyển đổi thủ công Cursor thành đối tượng.

Thư viện sử dụng một trình xử lý chú thích (kapt hoặc KSP) tạo ra việc triển khai các lớp trừu tượng RoomDatabase và DAO tại thời điểm xây dựng. Điều này đảm bảo rằng các lỗi cú pháp trong SQL và sự không khớp kiểu được phát hiện trước khi chạy ứng dụng, thay vì tại thời điểm chạy sau khi phát hành lên Google Play.

Theo Google I/O 2023, Room được sử dụng trong 68% ứng dụng Android làm việc với dữ liệu cục bộ. Đây là tiêu chuẩn lưu trữ dữ liệu trên thiết bị, được Google khuyến nghị cho tất cả các dự án mới — thay vì SQLiteOpenHelper và ContentProvider lỗi thời.

Tích hợp Room vào các dự án yêu cầu lưu cache cục bộ dữ liệu từ máy chủ, chế độ ngoại tuyến hoặc lưu trữ dữ liệu người dùng có cấu trúc với khả năng thực thi các truy vấn SQL phức tạp.

Room là một phần của Android Jetpack và được Google chính thức khuyến nghị cho tất cả các dự án mới làm việc với dữ liệu cục bộ. Không giống như Realm hay ObjectBox, Room sử dụng SQLite gốc, đảm bảo tương thích với bất kỳ công cụ cơ sở dữ liệu bên thứ ba nào — từ DB Browser đến DataGrip. Các nhà phát triển có thể mở tệp .db của ứng dụng và thực thi trực tiếp các truy vấn SQL, giúp đơn giản hóa việc gỡ lỗi và phân tích dữ liệu trong quá trình phát triển.

Entity và chú thích trong Room

Entity là một lớp dữ liệu được chú thích với @Entity mà Room chuyển đổi thành bảng cơ sở dữ liệu. Mỗi trường của lớp trở thành một cột của bảng và mỗi thể hiện trở thành một hàng. Room sử dụng phản xạ để truy cập các trường, vì vậy chú thích @PrimaryKey là bắt buộc cho một định danh.

Các chú thích chính

Chú thích @Entity cho Room biết lớp đó là một bảng. Tham số tableName đặt tên bảng nếu khác với tên lớp. @PrimaryKey định nghĩa khóa chính với hỗ trợ tự động tạo qua autoGenerate = true.

kotlin
@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    @ColumnInfo(name = "full_name")
    val name: String,
    @Ignore
    val tempData: String?
)

@ColumnInfo chỉ định tên cột trong bảng nếu khác với tên trường Kotlin. @Ignore loại trừ một trường khỏi bảng — nó sẽ không được lưu vào cơ sở dữ liệu. @ForeignKey mô tả khóa ngoại cho các mối quan hệ giữa các bảng với các thao tác cascade khi xóa hoặc cập nhật.

Room hỗ trợ các đối tượng lồng nhau thông qua chú thích @Embedded. Các trường của lớp lồng nhau được mở rộng thành các cột của bảng cha với tiền tố để tránh xung đột tên. Ví dụ: lớp Address với các trường city và street được nhúng trong User sẽ tạo các cột address_city và address_street trong bảng users, loại bỏ nhu cầu tạo bảng riêng cho các đối tượng giá trị đơn giản.

Bộ chuyển đổi kiểu

Room chỉ hỗ trợ các kiểu nguyên thủy và trình bao bọc của chúng. Để lưu trữ danh sách, Date hoặc kiểu tùy chỉnh, hãy sử dụng @TypeConverter — các phương thức tĩnh để chuyển đổi giữa kiểu tùy chỉnh và kiểu nguyên thủy SQLite, ví dụ: giữa List và chuỗi JSON.

DAO và truy vấn SQL

DAO (Đối tượng Truy cập Dữ liệu) là một giao diện hoặc lớp trừu tượng được chú thích với @Dao chứa các phương thức để truy cập dữ liệu. Mỗi phương thức được chú thích với một thao tác SQL: @Insert, @Update, @Delete hoặc @Query với một truy vấn SQL rõ ràng.

@Query với kiểm tra tại thời điểm biên dịch

Chú thích @Query nhận một chuỗi SQL mà Room kiểm tra tại thời điểm biên dịch về tính đúng đắn của cú pháp và sự khớp của tên cột với các trường Entity. Room hỗ trợ các truy vấn tham số hóa thông qua cú pháp :paramName.

kotlin
@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :userId")
    suspend fun getUserById(userId: Int): User?

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUser(user: User)

    @Query("SELECT * FROM users ORDER BY name ASC")
    fun getAllUsers(): Flow<List<User>>
}

@Insert hỗ trợ các chiến lược OnConflictStrategy để xử lý xung đột khi chèn các bản ghi trùng lặp. Flow như một kiểu trả về cung cấp các cập nhật giao diện người dùng phản ứng mỗi khi dữ liệu trong bảng thay đổi — đăng ký tự động khởi động lại khi có bất kỳ INSERT, UPDATE hoặc DELETE nào.

@Transaction cho các thao tác phức tạp

Chú thích @Transaction đảm bảo thực thi nguyên tử nhiều thao tác trong một khối giao dịch duy nhất. Room khóa cơ sở dữ liệu trong quá trình thực thi, ngăn chặn các điều kiện race khi truy cập đồng thời từ nhiều luồng.

Database và di chuyển schema

RoomDatabase là một lớp trừu tượng kết hợp Entity và DAO thành một điểm truy cập cơ sở dữ liệu duy nhất. Nó được tạo thông qua Room.databaseBuilder với phiên bản schema và danh sách các lớp Entity. Phiên bản cơ sở dữ liệu nên được tạo dưới dạng singleton thông qua ủy quyền lazy để tránh nhiều kết nối.

Di chuyển

Di chuyển trong Room là một lớp Migration mô tả tập lệnh SQL để chuyển từ phiên bản schema cũ sang phiên bản mới. Nếu không cung cấp di chuyển khi schema thay đổi, Room sẽ ném IllegalStateException. Điều này bảo vệ khỏi mất dữ liệu người dùng do vô tình khi cập nhật ứng dụng.

kotlin
val migration_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE users ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
    }
}

val db = Room.databaseBuilder(
    getApplication(),
    AppDatabase::class.java,
    "app_database"
).addMigrations(migration_1_2)
 .build()

Để phát triển, bạn có thể sử dụng fallbackToDestructiveMigration, thao tác này xóa cơ sở dữ liệu cũ và tạo mới khi không khớp phiên bản. Chế độ này chỉ dành cho gỡ lỗi — các bản phát hành sản xuất phải bao gồm các di chuyển phù hợp.

Để kiểm tra cơ sở dữ liệu, Room cung cấp một lớp đặc biệt Room.inMemoryTestBuilder tạo cơ sở dữ liệu trong bộ nhớ mà không lưu vào đĩa. Sau khi mỗi bài kiểm tra hoàn thành, cơ sở dữ liệu tự động bị hủy, đảm bảo sự cô lập hoàn toàn của các kịch bản kiểm tra. Kết hợp với thư viện android-arch-core-testing, các nhà phát triển có thể quản lý vòng đời cơ sở dữ liệu và xác minh tính đúng đắn của việc di chuyển mà không cần dọn dẹp trạng thái thủ công.

Hiệu suất của Room phụ thuộc trực tiếp vào cấu trúc truy vấn và chỉ mục. Để phân tích các truy vấn chậm, Room cung cấp cờ enableQueryCallback, ghi lại tất cả các truy vấn SQL với thời gian thực thi. Các nhà phát triển có thể sử dụng nhật ký này để tìm các truy vấn mất hơn 100 mili giây và tối ưu hóa chúng bằng cách thêm chỉ mục tổng hợp thông qua chú thích @Index trong @Entity hoặc viết lại các truy vấn con thành các thao tác JOIN trực tiếp bằng @Relation.

Room cũng hỗ trợ mã hóa cơ sở dữ liệu qua SQLCipher. Thêm thư viện net.zetetic:android-database-sqlcipher và sử dụng SupportFactory thay vì tiêu chuẩn cung cấp mã hóa minh bạch cho tất cả dữ liệu trên đĩa mà không thay đổi truy vấn DAO hoặc cấu trúc Entity. Điều này cần thiết cho các ứng dụng xử lý dữ liệu cá nhân của người dùng và tuân thủ GDPR cũng như Luật Liên bang Nga 152-FZ về bảo vệ dữ liệu cá nhân. Mật khẩu mã hóa có thể được lưu trữ trong Android Keystore để bảo vệ khỏi việc trích xuất qua các công cụ trên thiết bị đã root.

Room với Kotlin Coroutines và Flow

Room hỗ trợ Kotlin Coroutines từ phiên bản 2.1. Các phương thức DAO có thể là các hàm suspend thực thi truy vấn trong luồng nền mà không chặn luồng chính. Room tự động quản lý các bộ điều phối, sử dụng Dispatchers.IO cho các truy vấn đọc và ghi.

Đối với các truy vấn phản ứng, Room trả về một Flow — một luồng dữ liệu lạnh phát ra giá trị mới mỗi khi bảng bị ảnh hưởng thay đổi. ViewModel đăng ký Flow qua stateIn hoặc collect, cung cấp các cập nhật giao diện người dùng tự động mà không cần thông báo thủ công cho bộ điều hợp.

Room cũng hỗ trợ Paging 3 thông qua một triển khai PagingSource đặc biệt tải dữ liệu theo từng trang từ SQLite. Điều này hiệu quả cho các danh sách lớn với hàng nghìn bản ghi: Paging 3 chỉ tải các hàng hiển thị trên màn hình và tự động cập nhật chúng khi có thay đổi trong cơ sở dữ liệu.

Sử dụng Paging 3 với Room để hiển thị nguồn cấp tin tức, nhật ký hoạt động hoặc danh sách sản phẩm với truy cập ngoại tuyến và cuộn vô hạn.

Các câu hỏi thường gặp

Room khác SQLiteOpenHelper như thế nào?

Room tự động hóa việc tạo bảng, chuyển đổi Cursor thành đối tượng và kiểm tra SQL tại thời điểm biên dịch. SQLiteOpenHelper yêu cầu viết schema thủ công, xử lý Cursor và không có kiểm tra truy vấn trước khi chạy ứng dụng, làm tăng nguy cơ lỗi.

Tôi có cần viết di chuyển cho mỗi thay đổi schema không?

Có, khi thay đổi Entity (thêm/xóa trường, thay đổi kiểu), cần có di chuyển. Nếu không có, Room sẽ ném IllegalStateException khi khởi động. Để phát triển, bạn có thể bật fallbackToDestructiveMigration, nhưng các bản phát hành sản xuất yêu cầu tập lệnh di chuyển chính xác.

Room có hỗ trợ mối quan hệ giữa các bảng không?

Room hỗ trợ @ForeignKey cho các thao tác cascade và @Relation cho các đối tượng lồng nhau. Đối với các truy vấn JOIN phức tạp, hãy sử dụng chú thích @Transaction với @Query trả về POJO với các thực thể lồng nhau qua @Embedded và @Relation.

Tôi có thể sử dụng Room với Java mà không cần Kotlin không?

Có, Room hoàn toàn tương thích với Java. Thay vì các hàm suspend, hãy sử dụng LiveData hoặc RxJava Observable; thay vì Flow, hãy sử dụng LiveData. Room với Java hỗ trợ tất cả các chú thích tương tự, nhưng yêu cầu nhiều mã boilerplate hơn cho các thao tác bất đồng bộ.

Mã hóa cơ sở dữ liệu Room hoạt động như thế nào?

Room hỗ trợ mã hóa qua SQLCipher của Zetetic. Thay vì Room.databaseBuilder, hãy sử dụng SupportFactory từ thư viện net.zetetic:android-database-sqlcipher, truyền mật khẩu mã hóa. Tất cả dữ liệu trên đĩa sẽ được mã hóa minh bạch đối với các truy vấn DAO.

Tổng kết

  • Room là thư viện ORM của Jetpack cho SQLite với kiểm tra SQL tại thời điểm biên dịch.
  • @Entity mô tả bảng, @PrimaryKey là định danh, @ColumnInfo là tên cột.
  • @Dao chứa các phương thức với @Query, @Insert, @Update và @Delete để truy cập dữ liệu.
  • RoomDatabase kết hợp Entity và DAO, được tạo qua Room.databaseBuilder.
  • Di chuyển (Migration) mô tả tập lệnh SQL để thay đổi schema mà không mất dữ liệu.
  • Room hỗ trợ Kotlin Coroutines (suspend) và Flow để cập nhật giao diện người dùng phản ứng.
  • Đối với danh sách lớn, hãy sử dụng Paging 3 với Room qua PagingSource để tải theo trang.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm