Android Room 框架學習 一、Entity(實體) 二、DAO(數據訪問對象) 三、Database(數據庫) 四、數據遷移(升級) 五、數據庫信息的導出

       Room是一個對象關係映射(ORM)庫。Room抽象了SQLite的使用,可以在充分利用SQLite的同時訪問流暢的數據庫。

       Room官方文檔介紹 https://developer.android.com/training/data-storage/room/

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

  • Database:包含數據庫持有者,並作爲與應用持久關聯數據的底層連接的主要訪問點。而且Database對應的類必須滿足下面幾個條件:

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

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

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

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

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

  • DAO:包含用於訪問數據庫的方法。

       Room的使用也是需要添加依賴的

dependencies {
    ...

    // 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'

}

annotationProcessor 用於編譯期間根據註解(Annotation)獲取相關數據。

一、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.1、設置表的名字

       默認情況下Entity類的名字就是表的名字(不區分大小寫)。但是我們也可以通過@Entity的tableName屬性來自定義表名字。如下代碼所示users表對應的實體類。

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

1.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;
}

1.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屬性。

1.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;
}

1.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立即改變。

1.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的類或者接口。DAO是Room的主要組件,負責定義訪問數據庫的方法。Room使用過程中一般使用抽象DAO類來定義數據庫的CRUD操作。DAO可以是一個接口也可以是一個抽象類。如果它是一個抽象類,它可以有一個構造函數,它將RoomDatabase作爲其唯一參數。Room在編譯時創建每個DAO實。

       DAO裏面所有的操作都是依賴方法來實現的。

2.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.2、Update(更新)

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

       @Update和@Insert一樣也是可以設置onConflict來表明衝突的時候的解決辦法。

@Dao
public interface UserDao {

    @Update(onConflict = OnConflictStrategy.REPLACE)
    int updateUsers(User... users);

}

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

2.3、Delete(刪除)

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

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

@Dao
public interface UserDao {

    @Delete
    void deleteUsers(User... users);

}

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

2.4、Query(查詢)

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

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

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

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

2.4.1、簡單的查詢

       查詢所有的信息。

@Dao
public interface UserDao {

    @Query("SELECT * FROM user")
    User[] loadAllUsers();

}

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

2.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);

}

2.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();

}

2.4.4、查詢的時候傳遞一組參數

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

@Dao
public interface UserDao {

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

}

2.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的具體用法,我們這裏就不做過多的討論了。

2.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) {

                                }
                            });

2.4.7、查詢結果直接返回Cursor

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

@Dao
public interface UserDao {

    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    Cursor loadRawUsersOlderThan(int minAge);

}

關於怎麼從Cursor裏面去獲取結果,大家肯定都非常熟悉。

2.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導出到一個文件夾裏面。同時建議把這個文件夾上次到VCS。

       在運行時,你可以通過調用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文件夾下面。裏面會有各個版本數據庫的信息。


       本文涉及到的例子下載地址

       以上對Room做了一個簡單的介紹,如果大家使用Room的過程中有什麼疑問,也可以留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章