iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 15
0
Software Development

Android Architecture系列 第 15

Room - Relationships

  • 分享至 

  • xImage
  •  

Room提供三種方式做資料關聯:

  1. ForeignKey:update和delete時連動修改資料。
  2. Relation:方便查詢。
  3. TypeConverter:自訂資料型態。

以下將分別說明三種方式,示範用的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

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

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

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


上一篇
套用Repository Pattern完成最後架構
下一篇
Room - Migration
系列文
Android Architecture30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言