一、概述
Room提供了一個訪問SQLite的抽象層,以便在利用SQLite的全部功能的同時進(jìn)行流暢的數(shù)據(jù)庫訪問。
要在項(xiàng)目中使用Room需要在程序的build.gradle文件中添加以下依賴
dependencies {
def room_version = "2.1.0-alpha03"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version" // use kapt for Kotlin
// optional - RxJava support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// optional - Coroutines support for Room
implementation "androidx.room:room-coroutines:$room_version"
// Test helpers
testImplementation "androidx.room:room-testing:$room_version"
}
Room主要有以下三個部分組成:
- Database:
標(biāo)有@Database注解的類需要具備以下特征:- 繼承
RoomDatabase的抽象類 - 在注釋中包括與數(shù)據(jù)庫關(guān)聯(lián)的實(shí)體列表(@Database(entities ={ }))
- 包含一個無參的抽象方法并返回一個使用
@Dao注解的類
- 繼承
- Entity:對應(yīng)數(shù)據(jù)庫中的表
- DAO:包含訪問數(shù)據(jù)庫的方法
以上各部分的依賴關(guān)系如下圖所示:

下面使用一個簡單的例子介紹各部分如何使用:
User
@Entity
public class User {
@PrimaryKey
public int uid;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
}
UserDao
@Dao
public 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(User... users);
@Delete
void delete(User user);
}
AppDatabase
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
在創(chuàng)建完上面的文件之后,可以使用以下代碼獲得數(shù)據(jù)庫的實(shí)例:
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
在實(shí)例化AppDatabase對象時,應(yīng)遵循單例設(shè)計(jì)模式,因?yàn)槊總€
RoomDatabase實(shí)例都相當(dāng)消耗性能,并且您很少需要訪問多個實(shí)例。
二、 Entity定義數(shù)據(jù)
默認(rèn)情況下,Room為實(shí)體中定義的每個字段創(chuàng)建一個列。如果實(shí)體有不想持久的字段,則可以使用@Ignore來注解它們。必須通過Database類中的entities數(shù)組引用實(shí)體類。
下面的代碼片段顯示了如何定義實(shí)體:
@Entity
public class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
}
使用主鍵
每個實(shí)體必須定義至少1個字段作為主鍵。即使只有1個字段,仍然需要用@PrimaryKey注解字段。此外,如果您想Room自動分配IDs給實(shí)體,則可以設(shè)置@PrimaryKey的autoGenerate屬性。如果實(shí)體具有復(fù)合主鍵,則可以使用@Entity注解的primaryKeys屬性,如下面的代碼片段所示:
@Entity(primaryKeys = {"firstName", "lastName"})
public class User {
public String firstName;
public String lastName;
}
默認(rèn)情況下,Room使用類名作為數(shù)據(jù)庫表名。如果希望表具有不同的名稱,請?jiān)O(shè)置@Entity注解的tableName屬性
SQLite中的表名不區(qū)分大小寫。
與tableName屬性類似,Room使用字段名稱作為數(shù)據(jù)庫中的列名。如果希望列具有不同的名稱,請將@ColumnInfo注解添加到字段中,如下面的代碼片段所示:
@Entity(tableName = "users")
public class User {
@PrimaryKey
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
}
索引
需要索引數(shù)據(jù)庫中的某些列以加快查詢速度。若要向?qū)嶓w添加索引,在@Entity注釋中添加indices屬性 ,下面的代碼片段演示了這個注解過程:
@Entity(indices = {@Index("name"),
@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;
}
有時,數(shù)據(jù)庫中的某些字段或字段組必須是唯一的。可以通過將@Index注解的unique屬性設(shè)置為true來強(qiáng)制執(zhí)行此唯一性屬性。下面的代碼示例防止表中包含兩行,它們包含firstName和lastName列的相同值集:
@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;
}
三、Dao訪問數(shù)據(jù)
DAO既可以是接口,也可以是抽象類。如果是抽象類,它可以有一個構(gòu)造函數(shù),它把RoomDatabase作為唯一的參數(shù)。Room在編譯時創(chuàng)建每個DAO實(shí)現(xiàn)。
Insert
當(dāng)您創(chuàng)建一個DAO方法并用@Insert注解時,Room生成一個實(shí)現(xiàn),在一個事務(wù)中將所有參數(shù)插入到數(shù)據(jù)庫中
@Dao
public interface MyDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertUsers(User... users);
@Insert
public void insertBothUsers(User user1, User user2);
@Insert
public void insertUsersAndFriends(User user, List<User> friends);
}
如果@Insert方法只接收1個參數(shù),則可以返回一個Long型的值,這是插入項(xiàng)的新rowId。如果參數(shù)是數(shù)組或集合,則應(yīng)該返回long[] 或者 List類型的值。
有時插入數(shù)據(jù)和更新數(shù)據(jù)會產(chǎn)生沖突,所以就有了沖突之后要怎么解決,SQLite對于事務(wù)沖突定義了5個方案
OnConflictStrategy
- REPLACE:見名知意,替換,違反的記錄被刪除,以新記錄代替之
- ignore: 違反的記錄保持原貌,其它記錄繼續(xù)執(zhí)行
- fail: 終止命令,違反之前執(zhí)行的操作得到保存
- abort 終止命令,恢復(fù)違反之前執(zhí)行的修改
- rollback 終止命令和事務(wù),回滾整個事務(wù)
Update
@Update注解在數(shù)據(jù)庫中用于修改一組實(shí)體的字段。它使用每個實(shí)體的主鍵來匹配查詢。
@Dao
public interface MyDao {
@Update
public void updateUsers(User... users);
}
Delete
用于從數(shù)據(jù)庫中刪除給定參數(shù)的一系列實(shí)體,它使用主鍵匹配數(shù)據(jù)庫中相應(yīng)的行
@Dao
public interface MyDao {
@Delete
public void deleteUsers(User... users);
}
Query
@Query是DAO類中使用的主要注解。它允許您在數(shù)據(jù)庫上執(zhí)行讀/寫操作。每個@Query方法在編譯時被驗(yàn)證,因此,如果存在查詢問題,則會發(fā)生編譯錯誤而不是運(yùn)行時故障。
Room還驗(yàn)證查詢的返回值,這樣如果返回對象中字段的名稱與查詢響應(yīng)中的相應(yīng)列名不匹配,則Room將以以下兩種方式之一提醒您:
- 如果只有一些字段名匹配,則發(fā)出警告。
- 如果沒有字段名匹配,則會出錯。
1、簡單查詢
@Dao
public interface MyDao {
@Query("SELECT * FROM user")
public User[] loadAllUsers();
}
2、將參數(shù)傳遞到查詢中
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
}
3、傳遞參數(shù)集合
有些查詢可能要求您傳遞一個可變數(shù)量的參數(shù),其中參數(shù)的確切數(shù)目直到運(yùn)行時才知道。例如,您可能希望從區(qū)域的子集檢索有關(guān)所有用戶的信息
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}
4、Observable查詢
當(dāng)執(zhí)行查詢時,您經(jīng)常希望應(yīng)用程序的UI在數(shù)據(jù)更改時自動更新。要實(shí)現(xiàn)這一點(diǎn),請?jiān)诓樵兎椒枋鲋惺褂妙愋?code>LiveData的返回值。當(dāng)數(shù)據(jù)庫被更新時,Room生成所有必要的代碼來更新LiveData。
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}
5、RXJava的響應(yīng)式查詢
Room還可以從您定義的查詢中返回RXJava2 Publisher和Flowable對象。若要使用此功能,請將androidx.room:room-rxjava2庫添加到gradle的依賴關(guān)系中。
@Dao
public interface MyDao {
@Query("SELECT * from user where id = :id LIMIT 1")
public Flowable<User> loadUserById(int id);
// Emits the number of users added to the database.
@Insert
public Maybe<Integer> insertLargeNumberOfUsers(List<User> users);
// Makes sure that the operation finishes successfully.
@Insert
public Completable insertLargeNumberOfUsers(User... users);
/* Emits the number of users removed from the database. Always emits at
least one user. */
@Delete
public Single<Integer> deleteUsers(List<User> users);
}
6、直接Cursor訪問
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
public Cursor loadRawUsersOlderThan(int minAge);
}
四、 數(shù)據(jù)庫升級
在應(yīng)用程序中添加和更改特性時,你需要修改實(shí)體類以反映這些更改。當(dāng)用戶更新應(yīng)用程序到最新版本時,不希望它們丟失所有現(xiàn)有數(shù)據(jù),尤其是如果無法從遠(yuǎn)程服務(wù)器恢復(fù)數(shù)據(jù)。
如果您不提供必要的遷移,則Room會重新構(gòu)建數(shù)據(jù)庫,這意味著您將丟失數(shù)據(jù)庫中的所有數(shù)據(jù)
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");
}
};
五、類型轉(zhuǎn)換器
TypeConverter,它將自定義類轉(zhuǎn)換為Room可以保留的已知類型。例如,如果想要持久化實(shí)例Date,可以編寫以下內(nèi)容 TypeConverter 來在數(shù)據(jù)庫中存儲等效的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();
}
}
上面的例子中定義了兩個函數(shù),一個轉(zhuǎn)換Date對象到Long對象,另一個執(zhí)行逆變換,從Long到Date。由于Room是知道如何持久化Long對象的,因此它可以使用此轉(zhuǎn)換器來持久保存Date類型的值。接下來,添加 @TypeConverters 注釋到AppDatabase類,這樣Room就可以在AppDatabase中的實(shí)體和Dao上使用上面的定義的類型轉(zhuǎn)換器。還可以限制 @TypeConverters 到不同的范圍,包括單個實(shí)體,DAO和DAO方法。