Khi sử dụng thư viện Room về dữ liệu cố định để lưu trữ dữ liệu của ứng dụng, bạn sẽ tương tác với dữ liệu được lưu trữ bằng cách xác định các đối tượng truy cập dữ liệu hay còn gọi là DAO. Mỗi DAO có các phương thức cung cấp quyền truy cập trừu tượng vào cơ sở dữ liệu của ứng dụng. Vào thời gian biên dịch, Room sẽ tự động tạo hoạt động triển khai các DAO mà bạn xác định.
Bằng cách sử dụng DAO để truy cập cơ sở dữ liệu của ứng dụng thay vì các trình tạo truy vấn hoặc truy vấn trực tiếp, bạn có thể duy trì sự tách biệt các mối quan ngại – một nguyên tắc kiến trúc quan trọng. Các DAO cũng giúp bạn dễ dàng mô phỏng quyền truy cập cơ sở dữ liệu khi kiểm thử ứng dụng của bạn.
Phân tích thành phần của DAO
Bạn có thể xác định mỗi DAO là giao diện hoặc lớp trừu tượng. Đối với các trường hợp sử dụng cơ bản, bạn thường sử dụng giao diện. Trong cả hai trường hợp, bạn phải luôn chú giải các DAO của mình bằng @Dao
. Các DAO không có thuộc tính nhưng lại xác định một hoặc nhiều phương thức tương tác với dữ liệu trong cơ sở dữ liệu của ứng dụng.
Mã sau đây là ví dụ về một DAO đơn giản xác định các phương thức chèn, xoá và chọn đối tượng User
trong cơ sở dữ liệu Room:
Kotlin
@Dao interface UserDao { @Insert fun insertAll(vararg users: User) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List<User> }
Java
@Dao public interface UserDao { @Insert void insertAll(User... users); @Delete void delete(User user); @Query("SELECT * FROM user") List<User> getAll(); }
Có hai loại phương thức DAO để xác định các hoạt động tương tác với cơ sở dữ liệu:
- Các phương thức thuận tiện cho phép bạn chèn, cập nhật và xoá hàng trong cơ sở dữ liệu mà không cần ghi bất kỳ mã SQL nào.
- Các phương thức truy vấn cho phép bạn ghi truy vấn SQL của riêng bạn để tương tác với cơ sở dữ liệu.
Các mục sau đây minh hoạ cách sử dụng cả hai loại phương thức DAO để xác định các hoạt động tương tác với cơ sở dữ liệu mà ứng dụng của bạn cần.
Phương thức thuận tiện
Room cung cấp các chú giải thuận tiện để xác định các phương thức thực hiện thao tác chèn, cập nhật và xoá đơn giản mà không yêu cầu bạn ghi câu lệnh SQL.
Nếu bạn cần xác định các thao tác chèn, cập nhật hoặc xoá phức tạp hơn hoặc cần truy vấn dữ liệu trong cơ sở dữ liệu, hãy sử dụng phương thức truy vấn.
Chèn
Chú giải @Insert
cho phép bạn xác định các phương thức có thể chèn tham số của chúng vào bảng thích hợp trong cơ sở dữ liệu. Đoạn mã sau đây cho thấy ví dụ về các phương thức @Insert
hợp lệ có tác dụng chèn một hoặc nhiều đối tượng User
vào cơ sở dữ liệu:
Kotlin
@Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertUsers(vararg users: User) @Insert fun insertBothUsers(user1: User, user2: User) @Insert fun insertUsersAndFriends(user: User, friends: List<User>) }
Java
@Dao public interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) public void insertUsers(User... users); @Insert public void insertBothUsers(User user1, User user2); @Insert public void insertUsersAndFriends(User user, List<User> friends); }
Mỗi tham số cho một phương thức @Insert
phải là một phiên bản của lớp thực thể dữ liệu Room được chú giải bằng @Entity
hoặc một tập hợp các phiên bản lớp thực thể dữ liệu, mỗi phiên bản trỏ đến một cơ sở dữ liệu. Khi một phương thức @Insert
được gọi, Room sẽ chèn từng phiên bản thực thể đã truyền vào bảng cơ sở dữ liệu tương ứng.
Nếu nhận được một tham số duy nhất, thì phương thức @Insert
có thể trả về một giá trị long
(đây là giá trị rowId
mới cho mục được chèn). Nếu tham số là một mảng hoặc một tập hợp, thì phương thức sẽ trả về một mảng hoặc một tập hợp các giá trị long
thay thế, với mỗi giá trị là rowId
cho một trong các mục được chèn. Để tìm hiểu thêm về việc trả về các giá trị rowId
, hãy xem tài liệu tham khảo cho chú giải @Insert
và Tài liệu SQLite cho bảng
rowid.
Cập nhật
Chú giải @Update
cho phép bạn xác định các phương thức cập nhật những hàng cụ thể trong bảng cơ sở dữ liệu. Tương tự như phương thức @Insert
, phương thức @Update
chấp nhận các phiên bản thực thể dữ liệu dưới dạng tham số.
Đoạn mã sau đây cho thấy ví dụ về phương thức @Update
cố gắng cập nhật một hoặc nhiều đối tượng User
trong cơ sở dữ liệu:
Kotlin
@Dao interface UserDao { @Update fun updateUsers(vararg users: User) }
Java
@Dao public interface UserDao { @Update public void updateUsers(User... users); }
Room sử dụng khoá chính để so khớp các phiên bản thực thể đã truyền với các hàng trong cơ sở dữ liệu. Nếu không có hàng nào có cùng một khoá chính, Room sẽ không thực hiện thay đổi.
Phương thức @Update
có thể tuỳ ý trả về một giá trị int
cho biết số lượng hàng đã được cập nhật thành công.
Xoá
Chú giải @Delete
cho phép bạn xác định các phương thức xoá những hàng cụ thể khỏi bảng cơ sở dữ liệu. Tương tự như phương thức @Insert
, phương thức @Delete
chấp nhận các phiên bản thực thể dữ liệu dưới dạng tham số.
Đoạn mã sau đây cho thấy ví dụ về phương thức @Delete
cố gắng xoá một hoặc nhiều đối tượng User
khỏi cơ sở dữ liệu:
Kotlin
@Dao interface UserDao { @Delete fun deleteUsers(vararg users: User) }
Java
@Dao public interface UserDao { @Delete public void deleteUsers(User... users); }
Room sử dụng khoá chính để so khớp các phiên bản thực thể đã truyền với các hàng trong cơ sở dữ liệu. Nếu không có hàng nào có cùng một khoá chính, Room sẽ không thực hiện thay đổi.
Phương thức @Delete
có thể tuỳ ý trả về một giá trị int
cho biết số lượng hàng
đã được xoá thành công.
Phương thức truy vấn
Chú giải @Query
cho phép bạn ghi các câu lệnh SQL và hiển thị chúng dưới dạng các phương thức DAO. Hãy sử dụng các phương thức truy vấn này để truy vấn dữ liệu từ cơ sở dữ liệu của ứng dụng hoặc khi bạn cần thực hiện các thao tác chèn, cập nhật và xoá phức tạp hơn.
Room xác thực các truy vấn SQL vào thời gian biên dịch. Nói cách khác, nếu có vấn đề xảy ra với truy vấn của bạn, sẽ có lỗi biên dịch xảy ra thay vì lỗi thời gian chạy.
Truy vấn đơn giản
Mã sau đây xác định phương thức sử dụng một truy vấn SELECT
đơn giản để trả về tất cả các đối tượng User
trong cơ sở dữ liệu:
Kotlin
@Query("SELECT * FROM user") fun loadAllUsers(): Array<User>
Java
@Query("SELECT * FROM user") public User[] loadAllUsers();
Các mục sau đây minh hoạ cách sửa đổi ví dụ này cho các trường hợp sử dụng thông thường.
Trả về một tập hợp nhỏ các cột của bảng
Trong hầu hết trường hợp, bạn chỉ cần trả về một tập hợp nhỏ các cột từ bảng mà bạn đang truy vấn. Ví dụ: Giao diện người dùng có thể chỉ hiển thị họ và tên của người dùng thay vì mọi thông tin chi tiết về người dùng đó. Để tiết kiệm tài nguyên và đơn giản hoá quá trình thực thi truy vấn, hãy chỉ truy vấn các trường mà bạn cần.
Room cho phép bạn trả về một đối tượng đơn giản từ bất kỳ truy vấn nào, miễn là bạn có thể liên kết tập hợp các cột kết quả đến đối tượng được trả về. Ví dụ: Bạn có thể xác định đối tượng sau để giữ họ và tên của người dùng:
Kotlin
data class NameTuple( @ColumnInfo(name = "first_name") val firstName: String?, @ColumnInfo(name = "last_name") val lastName: String? )
Java
public class NameTuple { @ColumnInfo(name = "first_name") public String firstName; @ColumnInfo(name = "last_name") @NonNull public String lastName; }
Sau đó, bạn có thể trả về đối tượng đơn giản đó từ phương thức truy vấn:
Kotlin
@Query("SELECT first_name, last_name FROM user") fun loadFullName(): List<NameTuple>
Java
@Query("SELECT first_name, last_name FROM user") public List<NameTuple> loadFullName();
Room hiểu rằng truy vấn trả về các giá trị cho cột first_name
và
last_name
. Các giá trị này có thể được liên kết đến các trường trong lớp
NameTuple
. Nếu truy vấn trả về một cột không liên kết đến trường trong đối tượng được trả về, Room sẽ hiển thị cảnh báo.
Truyền các tham số đơn giản tới truy vấn
Trong hầu hết các trường hợp, các phương thức DAO của bạn cần chấp nhận các tham số để có thể thực hiện các thao tác lọc. Room hỗ trợ sử dụng các tham số phương thức làm tham số liên kết trong các truy vấn của bạn.
Ví dụ: Mã sau đây xác định một phương thức trả về tất cả người dùng trên một độ tuổi nhất định:
Kotlin
@Query("SELECT * FROM user WHERE age > :minAge") fun loadAllUsersOlderThan(minAge: Int): Array<User>
Java
@Query("SELECT * FROM user WHERE age > :minAge") public User[] loadAllUsersOlderThan(int minAge);
Bạn cũng có thể truyền nhiều tham số hoặc tham chiếu cùng một tham số nhiều lần trong một truy vấn, như được minh hoạ trong mã sau:
Kotlin
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge") fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User> @Query("SELECT * FROM user WHERE first_name LIKE :search " + "OR last_name LIKE :search") fun findUserWithName(search: String): List<User>
Java
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge") public User[] loadAllUsersBetweenAges(int minAge, int maxAge); @Query("SELECT * FROM user WHERE first_name LIKE :search " + "OR last_name LIKE :search") public List<User> findUserWithName(String search);
Truyền một tập hợp các tham số tới truy vấn
Một số phương thức DAO của bạn có thể yêu cầu bạn truyền số lượng tham số khác nhau chưa xác định cho đến thời gian chạy. Room hiểu khi một tham số đại diện cho một tập hợp và tự động mở rộng tập hợp đó trong thời gian chạy dựa trên số lượng tham số được cung cấp.
Ví dụ: Mã sau đây xác định một phương thức trả về thông tin liên quan đến tất cả người dùng từ một tập con các khu vực:
Kotlin
@Query("SELECT * FROM user WHERE region IN (:regions)") fun loadUsersFromRegions(regions: List<String>): List<User>
Java
@Query("SELECT * FROM user WHERE region IN (:regions)") public List<User> loadUsersFromRegions(List<String> regions);
Truy vấn nhiều bảng
Một số truy vấn của bạn có thể yêu cầu quyền truy cập vào nhiều bảng để tính toán kết quả. Bạn có thể sử dụng mệnh đề JOIN
trong các truy vấn SQL để tham chiếu nhiều bảng.
Mã sau đây xác định một phương thức kết hợp ba bảng với nhau để trả về kết quả các sách hiện đang cho một người dùng cụ thể mượn:
Kotlin
@Query( "SELECT * FROM book " + "INNER JOIN loan ON loan.book_id = book.id " + "INNER JOIN user ON user.id = loan.user_id " + "WHERE user.name LIKE :userName" ) fun findBooksBorrowedByNameSync(userName: String): List<Book>
Java
@Query("SELECT * FROM book " + "INNER JOIN loan ON loan.book_id = book.id " + "INNER JOIN user ON user.id = loan.user_id " + "WHERE user.name LIKE :userName") public List<Book> findBooksBorrowedByNameSync(String userName);
Bạn cũng có thể xác định các đối tượng đơn giản để trả về một tập con các cột từ nhiều bảng đã kết hợp như được thảo luận trong phần Trả về một tập con các cột của bảng. Mã sau đây xác định một DAO bằng phương thức trả về tên người dùng và tên sách mà họ đã mượn:
Kotlin
interface UserBookDao { @Query( "SELECT user.name AS userName, book.name AS bookName " + "FROM user, book " + "WHERE user.id = book.user_id" ) fun loadUserAndBookNames(): LiveData<List<UserBook>> // You can also define this class in a separate file. data class UserBook(val userName: String?, val bookName: String?) }
Java
@Dao public interface UserBookDao { @Query("SELECT user.name AS userName, book.name AS bookName " + "FROM user, book " + "WHERE user.id = book.user_id") public LiveData<List<UserBook>> loadUserAndBookNames(); // You can also define this class in a separate file, as long as you add the // "public" access modifier. static class UserBook { public String userName; public String bookName; } }
Trả về kiểu cấu trúc đa ánh xạ (multimap)
Trong Room 2.4 trở lên, bạn cũng có thể truy vấn các cột từ nhiều bảng mà không cần xác định thêm lớp dữ liệu bằng cách ghi các phương thức truy vấn trả về đa ánh xạ.
Xem ví dụ trong phần Truy vấn nhiều bảng.
Thay vì trả về danh sách các phiên bản của một lớp dữ liệu tuỳ chỉnh chứa cặp phiên bản User
và Book
, bạn có thể trực tiếp trả về một bản ánh xạ của User
và Book
từ phương thức truy vấn của mình:
Kotlin
@Query( "SELECT * FROM user" + "JOIN book ON user.id = book.user_id" ) fun loadUserAndBookNames(): Map<User, List<Book>>
Java
@Query( "SELECT * FROM user" + "JOIN book ON user.id = book.user_id" ) public Map<User, List<Book>> loadUserAndBookNames();
Khi phương thức truy vấn của bạn trả về đa ánh xạ, bạn có thể ghi các truy vấn sử dụng mệnh đề GROUP BY
, cho phép bạn tận dụng các khả năng của SQL để tính toán và lọc nâng cao. Ví dụ: Bạn có thể sửa đổi phương thức loadUserAndBookNames()
để chỉ trả về những người dùng đã mượn từ ba cuốn sách trở lên:
Kotlin
@Query( "SELECT * FROM user" + "JOIN book ON user.id = book.user_id" + "GROUP BY user.name WHERE COUNT(book.id) >= 3" ) fun loadUserAndBookNames(): Map<User, List<Book>>
Java
@Query( "SELECT * FROM user" + "JOIN book ON user.id = book.user_id" + "GROUP BY user.name WHERE COUNT(book.id) >= 3" ) public Map<User, List<Book>> loadUserAndBookNames();
Nếu không cần ánh xạ toàn bộ các đối tượng, bạn cũng có thể trả về các bản ánh xạ giữa các cột cụ thể trong truy vấn bằng cách đặt thuộc tính keyColumn
và valueColumn
trong chú giải @MapInfo
ở phương thức truy vấn của bạn:
Kotlin
@MapInfo(keyColumn = "userName", valueColumn = "bookName") @Query( "SELECT user.name AS username, book.name AS bookname FROM user" + "JOIN book ON user.id = book.user_id" ) fun loadUserAndBookNames(): Map<String, List<String>>
Java
@MapInfo(keyColumn = "userName", valueColumn = "bookName") @Query( "SELECT user.name AS username, book.name AS bookname FROM user" + "JOIN book ON user.id = book.user_id" ) public Map<String, List<String>> loadUserAndBookNames();
Loại dữ liệu trả về đặc biệt
Room cung cấp một số loại dữ liệu trả về đặc biệt để tích hợp với các thư viện API khác.
Truy vấn được phân trang bằng thư viện Paging
Room hỗ trợ các truy vấn phân trang thông qua việc tích hợp với thư viện Paging. Trong Room 2.3.0-alpha01 trở lên, DAO có thể trả về các đối tượng PagingSource
để sử dụng cùng với Paging 3.
Kotlin
@Dao interface UserDao { @Query("SELECT * FROM users WHERE label LIKE :query") fun pagingSource(query: String): PagingSource<Int, User> }
Java
@Dao interface UserDao { @Query("SELECT * FROM users WHERE label LIKE :query") PagingSource<Integer, User> pagingSource(String query); }
Để biết thêm thông tin về cách chọn tham số loại cho PagingSource
, xin xem phần Chọn loại khoá và giá trị.
Quyền truy cập con trỏ trực tiếp
Nếu logic của ứng dụng yêu cầu quyền truy cập trực tiếp vào các hàng trả về, bạn có thể ghi các phương thức DAO để trả về đối tượng Cursor
như minh hoạ trong ví dụ sau đây:
Kotlin
@Dao interface UserDao { @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5") fun loadRawUsersOlderThan(minAge: Int): Cursor }
Java
@Dao public interface UserDao { @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5") public Cursor loadRawUsersOlderThan(int minAge); }
Tài nguyên khác
Để tìm hiểu thêm về cách truy cập dữ liệu bằng DAO của Room, xem các tài nguyên bổ sung sau: