天天看點

Andoid Room基本使用方法特點三要素依賴使用資料遷移(更新)資料庫資訊的導出

版權聲明:本文轉載tuacy的博文

博文連結:https://blog.csdn.net/wuyuxing24/article/details/80773119

文章目錄

  • 特點
  • 三要素
  • 依賴
  • 使用
    • 1 Entity(實體)
      • 1.1 設定表的名字
      • 1.2 設定列的名字
      • 1.3 設定主鍵
      • 1.4 設定索引
      • 1.5 設定外鍵
      • 1.6 建立嵌套對象
    • 2 DAO(資料通路對象)
      • 2.1 Insert(插入)
      • 2.2 Update(更新)
      • 2.3 Delete(删除)
      • 2.4 Query(查詢)
    • 3 Database(資料庫)
  • 資料遷移(更新)
  • 資料庫資訊的導出

Room是一個對象關系映射(ORM)庫。Room抽象了SQLite的使用,充分利用SQLite強大功能的同時對資料庫進行流暢的通路。

特點

  1. 編譯時 sql 語句檢查

    Room 會在編譯階段檢查你的 DAO 中的 sql 語句,如果寫錯了(包括 sql 文法錯誤跟表名、字段名等等錯誤),會直接編譯失敗并提醒你哪裡出錯。

  2. sql 查詢直接關聯到 Java 對象
  3. 耗時操作主動要求異步處理

    Room 會在執行 db 操作時判斷是不是在 UI 線程,如果不是則app會直接挂掉 ,并提示讓你放到異步線程去做。

  4. 基于注解編譯時自動生成代碼
  5. API 設計符合 Sql 标準

三要素

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

  • Entity:代表資料庫中某個表的實體類。
  • Dao:用來處理資料庫操作,如增删改查,編譯的時候會生成_impl結尾的實作類,實作在DAO中定義的增删改查方法。 Entity 所有的 CRUD 業務代碼封裝在這裡。
  • Database:作為底層連接配接資料庫的主要接入點,它是一個抽象的類,并繼承RoomDatabase,編譯的時候會自動生成一個_impl結尾的實作類,實作資料庫以及表的建立及打開。

依賴

dependencies {
    ...
    implementation "android.arch.persistence.room:runtime:1.1.1"
    annotationProcessor 'android.arch.persistence.room:compiler:1.1.1'
    //annotationProcessor 用于編譯期間根據注解(Annotation)擷取相關資料。

    // room 配合 RxJava
    implementation "android.arch.persistence.room:rxjava2:1.1.1"
}
           

使用

1 Entity(實體)

每個Entity代表資料庫中某個表的實體類。預設情況下Room會把Entity裡面所有的字段對應到表上的每一列。Entity的實體類都需要添加@Entity注解。而且Entity類中需要映射到表中的字段需要保證外部能通路到這些字段(你要麼把字段什麼為public、要不實作字段的getter和setter方法)。

1.1 設定表的名字

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

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

1.2 設定列的名字

預設情況下Entity類中字段的名字就是表中列的名字。我們也是可以通過@ColumnInfo注解來自定義表中列的名字。如下代碼users表中first_name列對應firstName字段。

  • picture使用了@Ignore是以該字段不會映射到User表中。
@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(autoGenerate = true)
    public int _id;
    @PrimaryKey
    public String firstName;
    @PrimaryKey
    public String lastName;
}
           

如果你希望Room給entity設定一個自增的字段,可以設定@PrimaryKey的autoGenerate屬性,如上面的_id,autoGenerate = true 代表自動生成,而且會随着資料增加自增長。

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會将提供的值添加到嵌入對象中每個列名的開頭。

2 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都是表示插入的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 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、查詢傳回列的子集

有的時候可能指向傳回某些特定的列資訊。

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

}
           

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

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

}
           

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

3 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檔案夾下面。裡面會有各個版本資料庫的資訊。