Room提供三種方式做資料關聯:
以下將分別說明三種方式,示範用的Entity為User和Pet:
@Entity
public class User {
@PrimaryKey public String id;
public String name;
public String address;
public User(String id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
}
@Entity
public class Pet {
@PrimaryKey public String petId;
public String name;
public String userId;
public Pet(String petId, String name, String userId) {
this.petId = petId;
this.name = name;
this.userId = userId;
}
}
ForeignKey的特色為Update或Delete時可連動修改資料,當一個User有多個Pet時,在Pet的@Entity
加上foreignKeys:
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "userId",
onDelete = CASCADE))
public class Pet {
@PrimaryKey public String petId;
public String name;
public String userId;
public Pet(String petId, String name, String userId) {
this.petId = petId;
this.name = name;
this.userId = userId;
}
}
parentColumns表示User的欄位,childColumns是Pet本身的欄位,當資料有異動時我們可以選擇CASCADE、NO_ACTION、RESTRICT、SET_DEFAULT、SET_NULL這些連動,例如onDelete = CASCADE
表示當User被刪除時,關聯的全部Pet也一併刪除。
各個連動的運作方式可參考SQLite文件,其中Room有個不同的地方是Room的SET_DEFAULT等同於SET_NULL,因為Room目前還不支援欄位預設值功能。
另外需注意的是,SQLite對於@Insert(onConflict = REPLACE)
的處理是先delete舊資料再REPLACE,而不是單一的update動作,當使用ForeignKey時須小心先delete造成的影響。REPLACE的詳細運作可參考SQLite文件。
查詢時,兩者是用userId關聯,若要查詢一個User其所有的Pet:
@Query("SELECT * FROM Pet WHERE userId=:userId")
List<Pet> getPetsForUser(String userId);
Relation在查詢時比較方便,例如我們想要找出全部User以及他們的Pet,POJO像這樣:
public class UserAndAllPets {
public User user;
public List<Pet> pets;
}
那要得到List<UserAndAllPets>
的話,我們要下兩個查詢:先取得User列表,再跑迴圈取得他的Pet。
此時,@Relation
可以幫我們簡化成只要一個查詢,其特性是會撈出關聯的Entity。
修改POJO:
public class UserAndAllPets {
@Embedded
public User user;
@Relation(parentColumn = "id",
entityColumn = "userId")
public List<Pet> pets;
}
parentColumn為User中的欄位,entityColumn為Pet的欄位。
查詢語法可以簡化成這樣,只SELECT User時也能得到Pet:
@Query(“SELECT * FROM User”)
List<UserAndAllPets> getUserAndAllPets();
TypeConverter基本的用法是轉換資料型態,官方文件的舉例是想要儲存Date時,因為Android SQLite並不支援Date欄位,所以改成儲存Long並透過TypeConverter轉換。
public class Converters {
@TypeConverter
public static Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}
@TypeConverter
public static Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
這樣我們存取的時候可以直接用Date型態的欄位,TypeConverter會處理好轉換成Long的過程。
應用在資料關聯時,我們可以利用TypeConverter把物件轉成Json字串存起來,並在取出時轉回物件的型態。
要撈出所有User及他們的Pet時,我們可以在User中直接新增Pet欄位:
@Entity
public class User {
@PrimaryKey public String id;
public String name;
public String address;
public List<Pet> pets;
}
public class Pet {
public String petId;
public String name;
}
此時Pet就不用是Entity了,只是一般POJO。
使用TypeConverter將List<Pet>
存入資料庫時轉成Json字串,查詢時再parse成物件。
public class UserConverter {
private static Gson gson = new Gson();
private static Type petListType = new TypeToken<ArrayList<Pet>>() {}.getType();
@TypeConverter
public static List<Pet> petsFromJsonArray(String json) {
return gson.fromJson(json, petListType);
}
@TypeConverter
public static String petsToJsonArray(List<Pet> pets) {
return gson.toJson(pets);
}
}
在Database加上@TypeConverters
,這樣會套用到全部的Entity、DAO。如果只要在特定的Entity套用的話就改成加在Entity上面,其他可套用的地方詳見官方文件。
@Database(entities = {User.class}, version = 1)
@TypeConverters({UserConverter.class})
public abstract class AppDatabase extends RoomDatabase {
...
}
查詢語法如下,每個User中就有List<Pet>
可以直接使用。
@Query(“SELECT * FROM User”)
List<User> getUsers();
TypeConverter是運用空間比較大的方式,可以照我們的需求自訂儲存的型態,例如Day13中我們把整數列表轉成字串來存取。然而,因為每次存取都要處理型態的轉換,在使用上要留意效能問題。
Reference:
Defining data using Room entities
Android Architecture Components: Room — Relationships
7 Pro-tips for Room