Android框架式編程之Room


Room是Google官方出品的ORM(Object-relational mapping) 框架。當前我們也知道當前還有很多的ORM框架,例如GreenDao、OrmLite、Litepal等。目前並沒有深入了解這些框架,沒辦法比較各個框架的優缺點,但是相對而言,Room比較官方出品,且能夠更好的與LiveData及RxJava等框架結合使用,還是推薦各位學習和使用Room框架作為數據存儲的基礎框架的。官方文檔: https://developer.android.com/training/data-storage/room/

Room由三個重要的組件組成:Database、Entity、DAO。

Database

Database 是數據庫的持有者,是應用持久關聯數據的底層連接的主要訪問點。而且Database對應的類編寫時必須滿足下面幾個條件:

1. 必須是abstract類而且的extends RoomDatabase。

2. 必須在類頭的注釋中包含與數據庫關聯的實體列表(Entity對應的類)。

3. 包含一個具有0個參數的抽象方法,並返回用@Dao注解的類。

在運行時,你可以通過Room.databaseBuilder() 或者 Room.inMemoryDatabaseBuilder()獲取Database實例。

Entity

Entity代表數據庫中某個表的實體類。

DAO

DAO封裝用於訪問數據庫的方法。

一、Room 配置方式

在build.gradle中添加如下配置:

// add for room
implementation "android.arch.persistence.room:runtime:1.1.1"
// room 配合 RxJava
implementation "android.arch.persistence.room:rxjava2:1.1.1"
annotationProcessor 'android.arch.persistence.room:compiler:1.1.1'
// RxJava
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'io.reactivex.rxjava2:rxjava:2.1.3'

二、Entity(實體)

每個Entity代表數據庫中某個表的實體類。默認情況下Room會把Entity里面所有的字段對應到表上的每一列。

如果需要制定某個字段不作為表中的一列需要添加@Ignore注解,示例如下:

@Entity
public class User {

    @PrimaryKey
    public int id;

    public String firstName;
    public String lastName;

    @Ignore
    Bitmap picture;
}

示例中的picture字段代碼因為使用了@Ignore所以該字段不會映射到User表中。

Entity的實體類都需要添加@Entity注解,而且Entity類中需要映射到表中的字段需要保證外部能訪問到這些字段,這里建議把字段設置為public或者實現字段的getter和setter方法。

@Entity注解包含的屬性有:

  1. tableName:設置表名字。默認是類的名字。
  2. indices:設置索引。
  3. inheritSuperIndices:父類的索引是否會自動被當前類繼承。
  4. primaryKeys:設置主鍵。
  5. foreignKeys:設置外鍵。

1. 設置表的名字

默認情況下Entity類的名字就是表的名字(不區分大小寫)。但是我們也可以通過@Entity的tableName屬性來自定義表名字。

如下代碼所示users表對應的實體類。

@Entity(tableName = "users")
public class User {
    ...
}

2. 設置列的名字

默認情況下Entity類中字段的名字就是表中列的名字。我們也是可以通過@ColumnInfo注解來自定義表中列的名字。

如下代碼users表中first_name列對應firstName字段,last_name列對應lastName字段。

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

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

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

3. 設置主鍵

每個Entity都需要至少一個字段設置為主鍵。即使這個Entity只有一個字段也需要設置為主鍵。Entity設置主鍵的方式有兩種:

  • 通過@Entity的primaryKeys屬性來設置主鍵(primaryKeys是數組可以設置單個主鍵,也可以設置復合主鍵)。如下:
    @Entity(primaryKeys = {"firstName", "lastName"})
    public class User {
        public String firstName;
        public String lastName;
    }
  • 同@PrimaryKey注解來設置主鍵。如下:
    @Entity
    public class User {
        @PrimaryKey
        public String firstName;
        @PrimaryKey
        public String lastName;
    }

如果希望Room給entity設置一個自增的字段,可以設置@PrimaryKey的autoGenerate屬性。

4. 設置索引

 數據庫索引用於提高數據庫表的數據訪問速度的。數據庫里面的索引有單列索引和組合索引。Room里面可以通過@Entity的indices屬性來給表格添加索引。

@Entity(indices = {@Index("firstName"), @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;
}

索引也是分兩種的唯一索引和非唯一索引。唯一索引就想主鍵一樣重復會報錯的。可以通過的@Index的unique數學來設置是否唯一索引。

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

5. 設置外鍵

因為SQLite是關系型數據庫,表和表之間是有關系的。這也就是我們數據庫中常說的外鍵約束(FOREIGN KEY約束)。Room里面可以通過@Entity的foreignKeys屬性來設置外鍵。正常情況下,數據庫里面的外鍵約束。子表外鍵於父表。當父表中某條記錄子表有依賴的時候父表這條記錄是不能刪除的,刪除會報錯。一般大型的項目很少會采用外鍵的形式。一般都會通過程序依賴業務邏輯來保證的。

下面我們舉一個具體的例子來說明一下:

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

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

 上述代碼中,foreignKeys修飾后的Book表中的userId來源於User表中的id。

 @ForeignKey屬性介紹:

  • entity:parent實體類(引用外鍵的表的實體)。

  • parentColumns:parent外鍵列(要引用的外鍵列)。

  • childColumns:child外鍵列(要關聯的列)。

  • onDelete:默認NO_ACTION,當parent里面有刪除操作的時候,child表可以做的Action動作有:
           1. NO_ACTION:當parent中的key有變化的時候child不做任何動作。
           2. RESTRICT:當parent中的key有依賴的時候禁止對parent做動作,做動作就會報錯。
           3. SET_NULL:當paren中的key有變化的時候child中依賴的key會設置為NULL。
           4. SET_DEFAULT:當parent中的key有變化的時候child中依賴的key會設置為默認值。
           5. CASCADE:當parent中的key有變化的時候child中依賴的key會跟着變化。

  • onUpdate:默認NO_ACTION,當parent里面有更新操作的時候,child表需要做的動作。Action動作方式和onDelete是一樣的。

  • deferred:默認值false,在事務完成之前,是否應該推遲外鍵約束。這個怎么理解,當我們啟動一個事務插入很多數據的時候,事務還沒完成之前。當parent引起key變化的時候。可以設置deferred為ture。讓key立即改變。

6. 創建嵌套對象

有些情況下,你會需要將多個對象組合成一個對象。對象和對象之間是有嵌套關系的。Room中你就可以使用@Embedded注解來表示嵌入。然后,您可以像查看其他單個列一樣查詢嵌入字段。
比如有一個這樣的例子,User表包含的列有:id, firstName, street, state, city, and post_code。這個時候我們的嵌套關系可以用如下代碼來表示:
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;
}

@Embedded注解屬性:

  • prefix:如果實體具有多個相同類型的嵌入字段,則可以通過設置前綴屬性來保持每個列的唯一性。然后Room會將提供的值添加到嵌入對象中每個列名的開頭。

三、DAO(數據訪問對象)

DAO是Room的主要組件,負責定義訪問數據庫的方法。Room使用過程中一般使用抽象DAO類來定義數據庫的CRUD操作。

DAO可以是一個接口也可以是一個抽象類。如果它是一個抽象類,它可以有一個構造函數,它將RoomDatabase作為其唯一參數。Room在編譯時創建每個DAO實例。DAO里面所有的操作都是依賴方法來實現的。

1. Insert(插入)

當DAO里面的某個方法添加了@Insert注解。Room會生成一個實現,將所有參數插入到數據庫中的一個單個事務。

 @Insert注解可以設置一個屬性:
  • onConflict:默認值是OnConflictStrategy.ABORT,表示當插入有沖突的時候的處理策略。OnConflictStrategy封裝了Room解決沖突的相關策略:

       1. OnConflictStrategy.REPLACE:沖突策略是取代舊數據同時繼續事務。

       2. OnConflictStrategy.ROLLBACK:沖突策略是回滾事務。

       3. OnConflictStrategy.ABORT:沖突策略是終止事務。

       4. OnConflictStrategy.FAIL:沖突策略是事務失敗。

       5. OnConflictStrategy.IGNORE:沖突策略是忽略沖突。

一個簡單的實例如下:

@Dao
public interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertUsers(User... users);
}
當@Insert注解的方法只有一個參數的時候,這個方法也可以返回一個long,當@Insert注解的方法有多個參數的時候則可以返回long[]或者r List<Long>。long都是表示插入的rowId。

2. Update(更新)

當DAO里面的某個方法添加了@Update注解。Room會把對應的參數信息更新到數據庫里面去(會根據參數里面的primary key做更新操作)。

@Update和@Insert一樣也是可以設置onConflict來表明沖突的時候的解決辦法。
@Dao
public interface UserDao {
    @Update(onConflict = OnConflictStrategy.REPLACE)
    int updateUsers(User... users);
}

@Update注解的方法也可以返回int變量。表示更新了多少行。 

3. Delete(刪除)

當DAO里面的某個方法添加了@Delete注解。Room會把對應的參數信息指定的行刪除掉(通過參數里面的primary key找到要刪除的行)。

@Delete也是可以設置onConflict來表明沖突的時候的解決辦法。

@Dao
public interface UserDao {
    @Delete
    void deleteUsers(User... users);
}

@Delete對應的方法也是可以設置int返回值來表示刪除了多少行。

4. Query(查詢)

@Query注解是DAO類中使用的主要注釋。它允許您對數據庫執行讀/寫操作。@Query在編譯的時候會驗證准確性,所以如果查詢出現問題在編譯的時候就會報錯。

Room還會驗證查詢的返回值,如果返回對象中的字段名稱與查詢響應中的相應列名稱不匹配的時候,Room會通過以下兩種方式之一提醒您:

  • 如果只有一些字段名稱匹配,它會發出警告。
  • 如果沒有字段名稱匹配,它會發生錯誤。

@Query注解value參數:查詢語句,這也是我們查詢操作最關鍵的部分。

4.1 簡單的查詢

 查詢所有的信息。

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

}

返回結果可以是數組,也可以是List

4.2 帶參數的查詢

大多數情況下我們都需要查詢滿足特定的查詢條件的信息。

@Dao
public interface UserDao {
    @Query("SELECT * FROM user WHERE firstName == :name")
    User[] loadAllUsersByFirstName(String name);

}

查詢需要多個參數的情況

@Dao
public interface UserDao {

    @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
    User[] loadAllUsersBetweenAges(int minAge, int maxAge);

    @Query("SELECT * FROM user WHERE firstName LIKE :search " + "OR lastName LIKE :search")
    List<User> findUserWithName(String search);

}

4.3 查詢返回列的子集

有的時候可能指向返回某些特定的列信息。

下來的例子只查詢user表中的firstName和lastName信息。

@Entity
public class User {
    @PrimaryKey
    public String firstName;
    @PrimaryKey
    public String lastName;
    public int age;
}

public class NameTuple {

    private String firstName;
    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

@Dao
public interface UserDao {
    @Query("SELECT firstName, lastName FROM user")
    List<NameTuple> loadFullName();
}

4.4 查詢的時候傳遞一組參數

在查詢的時候您可能需要傳遞一組(數組或者List)參數進去。

@Dao
public interface UserDao {
    @Query("SELECT firstName, lastName FROM user WHERE region IN (:regions)")
    public List<NameTuple> loadUsersFromRegions(List<String> regions);
}

4.5 Observable的查詢

意思就是查詢到結果的時候,UI能夠自動更新。Room為了實現這一效果,查詢的返回值的類型為LiveData。

@Dao
public interface UserDao {
    @Query("SELECT firstName, lastName FROM user WHERE region IN (:regions)")
    LiveData<List<NameTuple>> loadUsersFromRegionsSync(List<String> regions);
}

關於LiveData的具體用法,我們這里就不做過多的討論了,后續的文章中我們會針對LiveData做詳細的說明。

4.6 使用RxJava作為查詢的返回值

Room的查詢也可以返回RxJava2的Publisher或者Flowable對象。當然了想要使用這一功能需要在build.gradle文件添加 implementation "android.arch.persistence.room:rxjava2:1.1.1" 依賴。

@Dao
public interface UserDao {
    @Query("SELECT * from user")
    Flowable<List<User>> loadUser();

}

 拿到Flowable<List<User>>數據之后就可以去調用

mAppDatabase.userDao()
       .loadUser()
       .subscribeOn(Schedulers.io())
       .observeOn(AndroidSchedulers.mainThread())
       .subscribe(new Consumer<List<User>>() {
              @Override
               public void accept(List<User> entities) {

               }
        });

4.7 查詢結果直接返回Cursor

查詢結果直接返回cursor。然后通過cursor去獲取具體的結果信息。

@Dao
public interface UserDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    Cursor loadRawUsersOlderThan(int minAge);
}

關於如何從Cursor里去獲取數據,大家肯定都非常熟悉,這里就不贅述了。

4.8 多表查詢

有的時候可能需要通過多個表才能獲取查詢結果。這個就涉及到數據的多表查詢語句了。

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

也可以查詢指定的某些列:

@Dao
public interface MyDao {
   @Query("SELECT user.name AS userName, pet.name AS petName "
          + "FROM user, pet "
          + "WHERE user.id = pet.user_id")
   public LiveData<List<UserPet>> loadUserAndPetNames();


   // You can also define this class in a separate file, as long as you add the
   // "public" access modifier.
   static class UserPet {
       public String userName;
       public String petName;
   }
}

五、Database(數據庫)

 @Database注解可以用來創建數據庫的持有者。該注解定義了實體列表,該類的內容定義了數據庫中的DAO列表。這也是訪問底層連接的主要入口點。注解類應該是抽象的並且擴展自RoomDatabase。

Database對應的對象(RoomDatabase)必須添加@Database注解,@Database包含的屬性:

  • entities:數據庫相關的所有Entity實體類,他們會轉化成數據庫里面的表。
  • version:數據庫版本。
  • exportSchema:默認true,也是建議傳true,這樣可以把Schema導出到一個文件夾里面。

在運行時,你可以通過調用Room.databaseBuilder()或者Room.inMemoryDatabaseBuilder()獲取實例。

因為每次創建Database實例都會產生比較大的開銷,所以應該將Database設計成單例的,或者直接放在Application中創建。

兩種方式獲取Database對象的區別:

  • Room.databaseBuilder():生成Database對象,並且創建一個存在文件系統中的數據庫。
  • Room.inMemoryDatabaseBuilder():生成Database對象並且創建一個存在內存中的數據庫。當應用退出的時候(應用進程關閉)數據庫也消失。

我們用一個簡單的實例來說明Database的創建。先定義一個abstract類AppDatabase繼承RoomDatabase:

@Database(entities = {User.class, Book.class}, version = 3)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
    public abstract BookDao bookDao();
}

創建RoomDatabase實例(AppDatabase)。這里我們把RoomDatabase實例的創建放在Application里面。

public class AppApplication extends Application {

    private AppDatabase mAppDatabase;

    @Override
    public void onCreate() {
        super.onCreate();
        mAppDatabase = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "android_room_dev.db")
                           .allowMainThreadQueries()
                           .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                           .build();
    }

    public AppDatabase getAppDatabase() {
        return mAppDatabase;
    }

    /**
     * 數據庫版本 1->2 user表格新增了age列
     */
    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE User ADD COLUMN age integer");
        }
    };

    /**
     * 數據庫版本 2->3 新增book表格
     */
    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL(
                "CREATE TABLE IF NOT EXISTS `book` (`uid` INTEGER PRIMARY KEY autoincrement, `name` TEXT , `userId` INTEGER, 'time' INTEGER)");
        }
    };
}

 創建RoomDatabase實例的時候,RoomDatabase.Builder類里面主要方法的介紹:

/**
     * 默認值是FrameworkSQLiteOpenHelperFactory,設置數據庫的factory。比如我們想改變數據庫的存儲路徑可以通過這個函數來實現
     */
    public RoomDatabase.Builder<T> openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory);

    /**
     * 設置數據庫升級(遷移)的邏輯
     */
    public RoomDatabase.Builder<T> addMigrations(@NonNull Migration... migrations);

    /**
     * 設置是否允許在主線程做查詢操作
     */
    public RoomDatabase.Builder<T> allowMainThreadQueries();

    /**
     * 設置數據庫的日志模式
     */
    public RoomDatabase.Builder<T> setJournalMode(@NonNull JournalMode journalMode);

    /**
     * 設置遷移數據庫如果發生錯誤,將會重新創建數據庫,而不是發生崩潰
     */
    public RoomDatabase.Builder<T> fallbackToDestructiveMigration();

    /**
     * 設置從某個版本開始遷移數據庫如果發生錯誤,將會重新創建數據庫,而不是發生崩潰
     */
    public RoomDatabase.Builder<T> fallbackToDestructiveMigrationFrom(int... startVersions);

    /**
     * 監聽數據庫,創建和打開的操作
     */
    public RoomDatabase.Builder<T> addCallback(@NonNull RoomDatabase.Callback callback);
RoomDatabase除了必須添加@Database注解也可以添加@TypeConverter注解。用於提供一個把自定義類轉化為一個Room能夠持久化的已知類型的,比如我們想持久化日期的實例,我們可以用如下代碼寫一個TypeConverter去存儲相等的Unix時間戳在數據庫中。
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();
    }
}

 六、數據遷移(升級)

大部分情況下設計的數據庫在版本的迭代過程中經常是會有變化的。比如突然某個表需要新加一個字段,需要新增一個表。這個時候我們又不想失去之前的數據。Room里面以Migration類的形式提供可一個簡化SQLite遷移的抽象層。Migration提供了從一個版本到另一個版本遷移的時候應該執行的操作。

       當數據庫里面表有變化的時候(不管你是新增了表,還是改變了某個表)有如下幾個場景。

  • 如果database的版本號不變。app操作數據庫表的時候會直接crash掉。(錯誤的做法)
  • 如果增加database的版本號。但是不提供Migration。app操作數據庫表的時候會直接crash掉。(錯誤的做法)
  • 如果增加database的版本號。同時啟用fallbackToDestructiveMigration。這個時候之前的數據會被清空掉。如下fallbackToDestructiveMigration()設置。(不推薦的做法)
    mAppDatabase = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "android_room_dev.db")
                          .allowMainThreadQueries()
                          .fallbackToDestructiveMigration()
                          .build();
  • 增加database的版本號,同時提供Migration。這要是Room數據遷移的重點。(最正確的做法)

所以在數據庫有變化的時候,我們任何時候都應該盡量提供Migrating。Migrating讓我們可以自己去處理數據庫從某個版本過渡到另一個版本的邏輯。我們用一個簡單的實例來說明。有這么個情況,數據庫開始設計的時候我們就一個user表(數據庫版本 1),第一次變化來了我們需要給user表增加一個age的列(數據庫版本 2),過了一段時間又有變化了我們需要新增加一個book表。三個過程版本1->2->3。

數據庫版本為1的時候的代碼,如下:

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

public class AppApplication extends Application {

    private AppDatabase mAppDatabase;

    @Override
    public void onCreate() {
        super.onCreate();
        mAppDatabase = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "android_room_dev.db")
                 .allowMainThreadQueries().build();
    }

    public AppDatabase getAppDatabase() {
        return mAppDatabase;
    }
    
}

數據庫版本為2的時候的代碼,User增加了age列:

@Entity
public class User {

    @PrimaryKey(autoGenerate = true)
    private long    uid;
    private String  name;
    private String  address;
    private String  phone;
    private Integer age;

    public long getUid() {
        return uid;
    }

    public void setUid(long uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

@Database(entities = {User.class}, version = 2)
public abstract class AppDatabase extends RoomDatabase {

    public abstract UserDao userDao();

}

public class AppApplication extends Application {

    private AppDatabase mAppDatabase;

    @Override
    public void onCreate() {
        super.onCreate();
        mAppDatabase = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "android_room_dev.db")
                           .allowMainThreadQueries()
                           .addMigrations(MIGRATION_1_2)
                           .build();
    }

    public AppDatabase getAppDatabase() {
        return mAppDatabase;
    }

    /**
     * 數據庫版本 1->2 user表格新增了age列
     */
    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE user " + " ADD COLUMN age INTEGER");
        }
    };
}

數據庫版本為3的時候的代碼,新增了一個Book表:

@Entity
public class Book {

    @PrimaryKey(autoGenerate = true)
    private Long   uid;
    private String name;
    private Date   time;
    private Long   userId;

    public Long getUid() {
        return uid;
    }

    public void setUid(Long uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getTime() {
        return time;
    }

    public void setTime(Date time) {
        this.time = time;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }
}

@Database(entities = {User.class, Book.class}, version = 3)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {

    public abstract UserDao userDao();

    public abstract BookDao bookDao();

}

public class AppApplication extends Application {

    private AppDatabase mAppDatabase;

    @Override
    public void onCreate() {
        super.onCreate();
        mAppDatabase = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "android_room_dev.db")
                           .allowMainThreadQueries()
                           .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                           .build();
    }

    public AppDatabase getAppDatabase() {
        return mAppDatabase;
    }

    /**
     * 數據庫版本 1->2 user表格新增了age列
     */
    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE User ADD COLUMN age integer");
        }
    };

    /**
     * 數據庫版本 2->3 新增book表格
     */
    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL(
                "CREATE TABLE IF NOT EXISTS `book` (`uid` INTEGER PRIMARY KEY autoincrement, `name` TEXT , `userId` INTEGER, 'time' INTEGER)");
        }
    };
}

Migrating使用過程中也有碰到一些坑,這里告誡大家一個經驗Entity中能用Integer的時候不用int。

七、數據庫信息的導出

Room也允許你會將你數據庫的表信息導出為一個json文件。你應該在版本控制系統中保存該文件,該文件代表了你的數據庫表歷史記錄,這樣允許Room創建舊版本的數據庫用於測試。

只需要在build.gradle文件中添加如下配置。編譯的時候就會導出json文件。

android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation":
                             "$projectDir/schemas".toString()]
            }
        }
    }
    // 用於測試
    sourceSets {
        androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
    }
}

 json文件文件會導出在工程目錄下的schemas文件夾下面。里面會有各個版本數據庫的信息。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM