一、簡介
Room是Google推出的Android架構組件庫中的數據持久化組件庫, 也可以說是在SQLite上實現的一套ORM解決方案。
Room主要包含三個部分:
其關系如下圖所示:

Room Architecture Diagram
二、基本使用
1. 創建Entity
1.1 一個簡單的Entitiy
一個簡單Entity定義如下:
@Entity(tableName = "user" indices = {@Index(value = {"first_name", "last_name"})})public class User { @PrimaryKey private int uid; @ColumnInfo(name = "first_name") private String firstName; @ColumnInfo(name = "last_name") private String lastName; @Ignore public User(String firstName, String lastName) { this.uid = UUID.randomUUID().toString(); this.firstName = firstName; this. lastName = lastName; } public User(String id, String firstName, String lastName) { this.uid = id; this.firstName = userName; this. lastName = userName; } // Getters and setters}1.2 Entitiy間的關系
不同于目前存在的大多數ORM庫,Room不支持Entitiy對象間的直接引用。
但Room允許通過外鍵(Foreign Key)來表示Entity之間的關系。
@Entity(foreignKeys = @ForeignKey(entity = User.class, parentColumns = "id", childColumns = "user_id"))class Book { @PrimaryKey public int bookId; public String title; @ColumnInfo(name = "user_id") public int userId;}如上面代碼所示,Book對象與User對象是屬于的關系。Book中的user_id,對應User中的id。 那么當一個User對象被刪除時, 對應的Book會發生什么呢?
@ForeignKey注解中有兩個屬性onDelete和onUpdate, 這兩個屬性對應ForeignKey中的onDelete()和onUpdate(), 通過這兩個屬性的值來設置當User對象被刪除/更新時,Book對象作出的響應。這兩個屬性的可選值如下:
1.3 對象嵌套
在某些情況下, 對于一張表中的數據我們會用多個POJO類來表示,在這種情況下可以用@Embedded注解嵌套的對象,比如:
class Address { public String street; public String state; public String city; @ColumnInfo(name = "post_code") public int postCode;}@Entityclass User { @PrimaryKey public int id; public String firstName; @Embedded public Address address;}以上代碼所產生的User表中,Column 為id, firstName, street, state, city, post_code
2. 創建數據訪問對象(DAO)
@Daopublic interface UserDao { @Query("SELECT * FROM user") List<User> getAll(); @Query("SELECT * FROM user WHERE uid IN (:userIds)") List<User> loadAllByIds(int[] userIds); @Query("SELECT * FROM user WHERE first_name LIKE :first AND " + "last_name LIKE :last LIMIT 1") User findByName(String first, String last); @Insert void insertAll(List<User> users); @Insert(onConflict = OnConflictStrategy.REPLACE) public void insertUsers(User... users); @Delete void delete(User user); @Update public void updateUsers(List<User> users);}DAO 可以是一個接口,也可以是一個抽象類, Room會在編譯時創建DAO的實現。
Tips:
DAO中的增刪改方法的定義都比較簡單,這里不展開討論,下面更多的聊一下查詢方法。
2.1 簡單的查詢
Talk is cheap, 直接show code:
@Query("SELECT * FROM user")List<User> getAll();Room會在編譯時校驗sql語句,如果@Query() 中的sql語句存在語法錯誤,或者查詢的表不存在,Room會在編譯時報錯。
2.2 查詢參數傳遞
@Query("SELECT * FROM user WHERE uid IN (:userIds)")List<User> loadAllByIds(int[] userIds);@Query("SELECT * FROM user WHERE first_name LIKE :first AND " + "last_name LIKE :last LIMIT 1")User findByName(String first, String last);看代碼應該比較好理解, 方法中傳遞參數arg, 在sql語句中用:arg即可。編譯時Room會匹配對應的參數。
如果在傳參中沒有匹配到:arg對應的參數, Room會在編譯時報錯。
2.3 查詢表中部分字段的信息
在實際某個業務場景中, 我們可能僅關心一個表部分字段的值,這時我僅需要查詢關心的列即可。
定義子集的POJO類:
public class NameTuple { @ColumnInfo(name="first_name") public String firstName; @ColumnInfo(name="last_name") public String lastName;}在DAO中添加查詢方法:
@Query("SELECT first_name, last_name FROM user")public List<NameTuple> loadFullName();這里定義的POJO也支持使用@Embedded
2.3 查詢結果的返回類型
Room中查詢操作除了返回POJO對象及其List以外, 還支持:
LiveData<T>:
LiveData是架構組件庫中提供的另一個組件,可以很好滿足數據變化驅動UI刷新的需求。Room會實現更新LiveData的代碼。
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);Flowablbe<T> Maybe<T> Single<T>:Room 支持返回RxJava2 的Flowablbe, Maybe和Single對象,對于使用RxJava的項目可以很好的銜接, 但需要在gradle添加該依賴:android/167410.html">android.arch.persistence.room:rxjava2。@Query("SELECT * from user where id = :id LIMIT 1")public Flowable<User> loadUserById(int id);Cursor:
返回Cursor是為了支持現有項目中使用Cursor的場景,官方不建議直接返回Cursor.
Caution: It's highly discouraged to work with the Cursor API because it doesn't guarantee whether the rows exist or what values the rows contain. Use this functionality only if you already have code that expects a cursor and that you can't refactor easily.
2.4 聯表查詢
Room支持聯表查詢,接口定義上與其他查詢差別不大, 主要還是sql語句的差別。
@Daopublic interface MyDao { @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);}3. 創建數據庫
Room中DataBase類似SQLite API中SQLiteOpenHelper,是提供DB操作的切入點,但是除了持有DB外, 它還負責持有相關數據表(Entity)的數據訪問對象(DAO), 所以Room中定義Database需要滿足三個條件:
@Database(entities = {User.class}, version = 1)public abstract class AppDatabase extends RoomDatabase { public abstract UserDao userDao();}創建好以上Room的三大組件后, 在代碼中就可以通過以下代碼創建Database實例。
AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name").build();
三、數據庫遷移
3.1 Room數據庫升級
在傳統的SQLite API中,我們如果要升級數據庫, 通常在SQLiteOpenHelper.onUpgrade方法執行數據庫升級的sql語句,這些sql語句的通常根據數據庫版本以文件的方式或者用數組來管理。有人說這種方式升級數據庫就像在拆炸彈,相比之下在Room中升級數據庫簡單的就像是按一個開關而已。
Room提供了Migration類來實現數據庫的升級:
Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name") .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, " + "`name` TEXT, PRIMARY KEY(`id`))"); }};static final Migration MIGRATION_2_3 = new Migration(2, 3) { @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL("ALTER TABLE Book " + " ADD COLUMN pub_year INTEGER"); }};在創建Migration類時需要指定startVersion和endVersion, 代碼中MIGRATION_1_2和MIGRATION_2_3的startVersion和endVersion是遞增的, Migration其實是支持從版本1直接升到版本3,只要其migrate()方法里執行的語句正常即可。那么Room是怎么實現數據庫升級的呢?其實本質上還是調用SQLiteOpenHelper.onUpgrade,Room中自己實現了一個SQLiteOpenHelper, 在onUpgrade()方法被調用時觸發Migration,當第一次訪問數據庫時,Room做了以下幾件事:
這樣一看, Room中處理數據庫升級確實很像是加一個開關。
3.2 原有SQLite數據庫遷移至Room
因為Room使用的也是SQLite, 所以可以很好的支持原有Sqlite數據庫遷移到Room。
假設原有一個版本號為1的數據庫有一張表User, 現在要遷移到Room, 我們需要定義好Entity, DAO, Database, 然后創建Database時添加一個空實現的Migraton即可。需要注意的是,即使對數據庫沒有任何升級操作,也需要升級版本, 否則會拋異常IllegalStateException.
@Database(entities = {User.class}, version = 2)public abstract class UsersDatabase extends RoomDatabase {…static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) { // Since we didn't alter the table, there's nothing else to do here. }};…database = Room.databaseBuilder(context.getApplicationContext(), UsersDatabase.class, "Sample.db") .addMigrations(MIGRATION_1_2) .build();四、復雜數據的處理
在某些場景下我們的應用可能需要存儲復雜的數據類型,比如Date,但是Room的Entity僅支持基本數據類型和其裝箱類之間的轉換,不支持其它的對象引用。所以Room提供了TypeConverter給使用者自己實現對應的轉換。
一個Date類型的轉換如下:
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(); }}定義好轉換方法后,指定到對應的Database上即可, 這樣就可以在對應的POJO(User)中使用Date類了。
@Database(entities = {User.class}, version = 1)@TypeConverters({Converters.class})public abstract class AppDatabase extends RoomDatabase { public abstract UserDao userDao();}@Entitypublic class User { ... private Date birthday;}五、總結
在SQLite API方式實現數據持久化的項目中,相信都有一個任務繁重的SQLiteOpenHelper實現, 一堆維護表的字段的Constant類, 一堆代碼類似的數據庫訪問類(DAO),訪問數據庫時需要做Cursor的遍歷,構建并返回對應的POJO類...相比之下,Room作為在SQLite之上封裝的ORM庫確實有諸多優勢,比較直觀的體驗是:
總結
以上所述是小編給大家介紹的Android架構組件Room指南,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對VEVB武林網網站的支持!
新聞熱點
疑難解答