Giới thiệu về Room Persistence Library

Room là một abstract layer cung cấp các cách thức truy cập cơ sở dữ liệu SQLite. Các lợi ích mà Room đem lại:

  • Đơn giản hoá các hoạt động liên quan đến cơ sở dữ liệu.
  • Xác thực các câu truy vấn tại thời điểm biên dịch, điều này giúp tránh các lỗi về cơ sở dữ liệu xảy ra khi ứng dụng đang hoạt động.

Để sử dụng Room library, cần thêm vào dependencies trong file build.gradle (app):

implementation 'android.arch.persistence.room:runtime:1.1.1'
annotationProcessor 'android.arch.persistence.room:compiler:1.1.1'

1. Các thành phần chính của Room

Room Architecture Room bao gồm 3 thành phần chính:

  • Entity: Class được lưu trữ trong database. Mỗi class được đánh dấu với annotation @Entiy tương ứng với một bảng trong cơ sở dữ liệu. Mỗi thực thể tương ứng với một dòng trong bảng.
  • DAO (Data Access Object): Bao gồm các phương thức để thao tác với cơ sở dữ liệu.
  • Database: Bao gồm database holder và đóng vai trò là điểm truy cập đến cơ sở dữ liệu.

2. Entity

Với mỗi một class được đánh dấu @Enity, một bảng tương ứng được tạo ra trong cơ sở dữ liệu. Mặc định, Room tạo ra mỗi cột ứng với một trường trong class. Tuy nhiên, nếu không muốn lưu trữ một trường nào đó, bạn có thể đánh dấu bằng annotation @Ignore.

Ví dụ:

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

    public String firstName;
    public String lastName;

    @Ignore
    Bitmap picture;
}

Primary key

Mỗi entity phải định nghĩa ít nhất một trường làm khoá chính (kể cả trường hợp chỉ có một trường). Trường được chọn làm khoá chính được chú thích bằng annotation @PrimaryKey như trong ví dụ trên.

Nếu muốn định nghĩa nhiều trường làm khoá chính, có thể sử dụng thuộc tính primaryKeys của annotation @Entity. Ví dụ:

@Entity(primaryKeys = {"firstName", "lastName"})
public class User {
    public String firstName;
    public String lastName;

    @Ignore
    Bitmap picture;
}

Custom tên bảng, tên trường

Mặc định, Room sử dụng tên class để đặt tên cho các bảng. Để đặt tên khác cho bảng, sử dụng thuộc tính tableName của @Entity. Tương tự, Room cũng sử dụng tên các trường trong class để đặt tên các cột trong bảng. Để thay đổi tên mặc định cho các cột, sử dụng annotation @ColumnInfo với thuộc tính name. Ví dụ:

@Entity(tableName = "users")
public class User {
    @PrimaryKey
    public int id;

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

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

    @Ignore
    Bitmap picture;
}

Indices và Uniqueness

Trường hợp muốn đánh chỉ mục để tăng tốc độ truy vấn có thể sử dụng thuộc tính indices trong annotation @Enitity. Ví dụ:

@Entity(indices = {@Index("name"),
        @Index(value = {"last_name", "address"})})
public class User {
    @PrimaryKey
    public int id;

    public String firstName;
    public String address;

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

    @Ignore
    Bitmap picture;
}

Trường hợp muốn một trường là duy nhất trong database, có thể gán thuộc tính uniquetrue.

@Entity(indices = {@Index(value = {"first_name", "last_name"},
        unique = true)})
public class User {
    @PrimaryKey
    public int id;

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

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

    @Ignore
    Bitmap picture;
}

Quan hệ giữa các đối tượng

Room cho phép định nghĩa quan hệ giữa các đối tượng thông qua khoá ngoài. Ví dụ, có một entity khác là Book, bạn có thể định nghĩa quan hệ với enity User thông qua annotation @ForeignKey.

@Entity(foreignKeys = @ForeignKey(entity = User.class,
                                  parentColumns = "id",
                                  childColumns = "user_id"))
public class Book {
    @PrimaryKey
    public int bookId;

    public String title;

    @ColumnInfo(name = "user_id")
    public int userId;
}

Nested object

Xét ví dụ class Address bao gồm các trường street, city, statepostCode, class User có một trường có kiểu Address. Tuy nhiên, bạn lại không có nhu cầu lưu Address thành một bảng riêng, mà muốn bảng User chứa các thuộc tính của Address. Trong trường hợp này có thể sử dụng annotation Embedded.

public class Address {
    public String street;
    public String state;
    public String city;

    @ColumnInfo(name = "post_code")
    public int postCode;
}

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

    public String firstName;

    @Embedded
    public Address address;
}

Khi đó, bảng biểu diễn class User bao gồm các trường id, firstName, street, state, city, post_code.

3. DAO (Data Access Object)

DAO là thành phần chịu trách nhiệm định nghĩa các phương thức truy cập cơ sở dữ liệu. DAO có thể là một interface hoặc abtract class. Tại thời điểm biên dịch, Room tạo ra một implementation của class này.

Các thao tác cơ bản với database

Room cho phép định nghĩa các thao tác thêm, sửa, xoá dữ liệu thông qua các annotation @Insert, @Update, @Delete. Ta có ví dụ sau:

@Dao
public interface MyDao {
    @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);

    @Update
    public void updateUsers(User... users);

    @Delete
    public void deleteUsers(User... users);
}

Định nghĩa câu truy vấn

@Query là annotation chính được sử dụng với DAO, nó cho phép thực hiện đọc, ghi trên cơ sở dữ liệu. Mỗi phương thức được đánh dấu @Query sẽ được kiểm tra ở thời điểm biên dịch. Điều này nghĩa là, nếu câu truy vấn bị lỗi, sẽ có lỗi compile thay vì lỗi runtime.

Ví dụ một câu truy vấn đơn giản:

@Dao
public interface MyDao {
    @Query("SELECT * FROM user")
    public User[] loadAllUsers();
}

Ngoài ra, có thể truyền tham số vào trong câu truy vấn.

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge")
    public User[] loadAllUsersOlderThan(int minAge);

    @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);
}

4. Database

Database class được đánh dấu bằng annotation @Database và phải thoả mãn các điều kiện:

  • Phải là abstract class kế thừa class RoomDatabase.
  • Bao gồm danh sách các entity liên kết với cơ sở dữ liệu.
  • Bao gồm một abstract method không có tham số truyền vào và trả về một class được đánh dấu với annotation @Dao.

Ví dụ:

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

Tại runtime, bạn có thể gọi một instance của Database bằng cách gọi phương thức Room.databaseBuilder() hoặc Room.inMemoryDatabaseBuilder().

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

Demo

Trên đây là những tìm hiểu cơ bản của mình về Room Library. Các bạn có thể xem ứng dụng demo của mình tại đây.

Tài liệu tham khảo

Google Guide for Android developer