Nội dung
1. Giới thiệu
Xin chào các bạn, hôm nay mình xin viết một bài chia sẻ về ROOM database trong android. Vậy tại sao phải dùng ROOM, sao không dùng SQLite thuần tuý? Có 2 nguyên nhân dưới đây:
- Bạn phải viết mã lặp đi lặp lại dài, mà sẽ tốn thời gian cũng như dễ mắc lỗi.
- Rất khó để quản lý các truy vấn SQL cho một cơ sở dữ liệu quan hệ phức tạp. Room được google giới thiệu là 1 ORM mạnh , hữu ích cho các developper, giúp giải quyết các vấn đề trên. Dưới đây là 1 số phương thức mà Room đã định nghĩa sẵn cho ta:
Các ứng dụng sử dụng một lượng lớn dữ liệu có cấu trúc có thể hưởng lợi lớn từ việc lưu lại dữ liệu trên local thông qua Room Database. Trường hợp thường gặp nhất là chỉ cache những dữ liệu có liên quan. Nếu làm vậy thì khi thiết bị không có kết nối internet thì user vẫn có thể truy cập data đấy khi đang offline. Mọi dữ liệu được phát sinh hay thay đổi do user sau đó sẽ được đồng bộ với server khi họ online trở lại.
Thật đơn giản và ngắn gọn phải không nào.! Nếu các bạn đã chán ngán với việc phải khai báo các câu lệnh rất dài mới có thể xây dựng được data base thì hãy sử dụng Room ngay nhé
2. Đặc điểm của Room database
Framework chính (Sqlite Database) cung cấp các built-in support cho các trường hợp làm việc với các nội dung SQL thô. Mặc dù các API này khá mạnh mẽ nhưng chúng lại tương đối low-level và yêu cầu khá nhiều thời gian và nỗ lực để sử dụng:
- Không có xác thực các câu truy vấn SQL ở thời điểm compile-time. Khi data graph thay đổi thì dev sẽ phải cập nhật lại các câu truy vấn SQL thủ công. Việc này khá mất thời gian và xác suất gặp lỗi trong quá trình khá lớn.
- Sẽ phải dùng nhiều code khung để chuyển đổi giữa truy vấn SQL với các Java data object (Phần này chắc ai làm việc với DB nhiều chắc chắn hiểu rõ)
Room sẽ giải quyết cả hai vấn đề này thay cho dev.
Import Roomdatan
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
}
}
Mở build.gradle (app) và thêm dòng lệnh sau trong dependencies
implementation "android.arch.persistence.room:runtime:1.0.0-beta2"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-beta2"
Các bạn chú ý update phiên bản mới nhất của Room nhé, hiện tại là 1.0.0-beta2 Bạn nào sử dụng RxJava kết hợp với Room database thì implement thêm rxjava nhé
implementation "android.arch.persistence.room:rxjava2:1.0.0-beta2"
3. Các thành phần chính trong Room
3.1 Entity
Component này đại diện cho một class chứa một row của database. Với mỗi một entity thì một database table sẽ được tạo để giữ các items tương ứng. Nên tham chiếu lớp enity thông qua mảng entities trong class Database. Mỗi một trường của enitty sẽ được pesist trong database trừ trường hợp bị chú thích là @Ignore. • Note: Các entity có thể hoặc là có hàm khởi tạo rỗng (trường hợp lớp DAO có thể truy cập từng field đã persist) hoặc là hàm khởi tạo với các đối số là các kiểu dữ liệu và tên khớp với một trong các field của entity. Room còn có thể sử dụng hàm khởi tạo đầy đủ hoặc một phần, ví dụ như hàm khởi tạo chỉ nhận một trong các field.
Bài toán: Lưu lại toàn bộ các user và add vào database, đối tượng user gồm các thuộc tính name, password và place
User.java
@Entity(tableName = "users")
public class User {
private static final String DEFAULT_PW = "12345678";
@NonNull
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
private int mId;
@ColumnInfo(name = "first_name")
private String mFirstName;
@ColumnInfo(name = "last_name")
private String mLastName;
@ColumnInfo(name = "password")
private String mPassword;
@Embedded
private Place mPlace;
public User() {
}
@Ignore
public User(String firstName, String lastName) {
mFirstName = firstName;
mLastName = lastName;
mPassword = DEFAULT_PW;
}
@Override
public String toString() {
if (mPlace != null) {
return mFirstName + " " + mLastName + "\n" + mPlace.getName();
}
return mFirstName + " " + mLastName;
}
}
Place.java
@Entity(tableName = "place")
public class Place {
@PrimaryKey(autoGenerate = true)
private int mId;
@ColumnInfo(name = "lat")
private double mLat;
@ColumnInfo(name = "lng")
private double mLng;
@ColumnInfo(name = "name")
private String mName;
public Place() {
}
}
3.2 Entity
Vơi mỗi Object được định nghĩa với anotation @Entity Room sẽ tạo một table cho đối tượng này trong database với name là tableName được chú thích. Room sẽ tạo các cột tương ứng với số field được khai báo trong object. Nếu không muốn lưu trữ bạn có thể sử dụng anotation @Ignore Bạn có thể custom lại tên của cột thông qua anotaion @ColumnInfo(name = “column name”)
3.3 Primary key
Mỗi Object phải xác định ít nhất 1 trường làm khóa chính. Ngay cả khi chỉ có 1 trường, bạn vẫn cần chú thích trường này bằng anotation @PrimaryKey. Ngoài ra, nếu bạn muốn Room gán ID tự động cho các thực thể, bạn có thể đặt thuộc tính autoGenerate của @ PrimaryKey.(Trường hợp thuộc tính là int, long)
@PrimaryKey(autoGenerate = true)
private int mId;
3.4 Indices and uniqueness
Trường hợp các bạn muốn đánh index cho một số trường trong database để tăng tốc độ truy vấn các bạn có thể sử dụng như sau
@Entity(indices = {@Index(value = {"first_name", "last_name"}})
Một số trường hợp các bạn có thể muốn một số trường là duy nhất trong db ví dụ first_name và last_name không thể có bản ghi nào trùng nhau các bạn có thể thêm unique như sau
@Entity(indices = {@Index(value = {"first_name", "last_name"},
unique = true)}
3.5 Nested objects
Trong một số trường hợp các bạn tạo ra object với các nested object, các bạn không có nhu cầu lưu chúng thành 1 bảng riêng mà đơn giản chỉ giống như 1 column bình thường các bạn có thể sử dụng anotaion @Embedded cho chúng giống như mình đã làm cho Place trong object User
@Embedded
private Place mPlace;
3.6 Relationships between objects
Định nghĩa foreignKeys Ví dụ bạn có đối tượng khác là Pet.java và bạn có thể định nghĩa relationship tới đối tượng User.java thông qua @ForeignKey annotation như sau
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "user_id"))
class Pet {
@PrimaryKey
public int petId;
public String name;
@ColumnInfo(name = "user_id")
public int userId;
}
4. DAO (Data Access Objects)
Đây là component đại diện cho lớp hoặc interface như một đối tượng truy cập dữ liệu (DAO). DAO là thành phần chính của Room là chịu trách nhiệm trong việc định nghĩa các phương thức truy cập CSDL. Các lớp được chú thích với @Database phải chứa một phương thức trừu tượng có số lượng đối số truyền vào là 0 và đối tượng trả về là đối tượng của lớp được chú thích bởi @Dao. Khi code được sinh ra ở thời điểm biên dịch thì Room sẽ tạo một implementation của class này. • Note: Bằng cách truy cập database sử dụng lớp DAO thay vì query builder hoặc queries trực tiếp thì dev có thể cô lập các thành phần khác nhau của kiến trúc database. Hơn nữa, DAO cho phép dev dễ dàng mock truy cập database khi dev test app.
@Dao
public interface UserDAO {
@Query("SELECT * FROM users WHERE id = :userId")
Flowable<User> getUserByUserId(int userId);
@Query("SELECT * FROM users WHERE first_name LIKE :userName OR last_name LIKE :userName")
Flowable<List<User>> getUserByName(String userName);
@Query("SELECT * FROM users")
Flowable<List<User>> getALlUser();
@Insert
void insertUser(User... users);
@Delete
void deleteUser(User user);
@Query("DELETE FROM users")
void deleteAllUser();
@Update
void updateUser(User... users);
}
4.1. Database
Có thể dùng componenet này để tạo database holder. Annotation sẽ cung cấp danh sách các thực thể và nội dung class sẽ định nghĩa danh sách các DAO (đối tượng truy cập CSDL) của CSDL. Nó cũng là điểm truy cập chính cho các kết nối phía dưới. Annotated class nên để là lớp abstract extends RoomDatabase. Tại thời điểm runtime thì dev có thể nhận được một instance của nó bằng cách gọi Room.databaseBuilder() hoặc Room.inMemoryDatabaseBuilder().
@Database(entities = {User.class}, version = DATABASE_VERSION)
public abstract class UserDatabase extends RoomDatabase{
private static UserDatabase sUserDatabase;
public static final int DATABASE_VERSION = 2;
public static final String DATABASE_NAME = "Room-database";
public abstract UserDAO userDAO();
public static UserDatabase getInstance(Context context) {
if (sUserDatabase == null) {
sUserDatabase = Room.databaseBuilder(context, UserDatabase.class, DATABASE_NAME)
.fallbackToDestructiveMigration()
.build();
}
return sUserDatabase;
}
}