一锌云、概述
Room提供了一個訪問SQLite的抽象層葬燎,以便在利用SQLite的全部功能的同時進(jìn)行流暢的數(shù)據(jù)庫訪問辫呻。
要在項目中使用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)的實體列表(@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ù)庫的實例:
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
在實例化AppDatabase對象時劳较,應(yīng)遵循單例設(shè)計模式驹止,因為每個
RoomDatabase
實例都相當(dāng)消耗性能浩聋,并且您很少需要訪問多個實例。
二幢哨、 Entity定義數(shù)據(jù)
默認(rèn)情況下赡勘,Room為實體中定義的每個字段創(chuàng)建一個列。如果實體有不想持久的字段捞镰,則可以使用@Ignore
來注解它們闸与。必須通過Database類中的entities
數(shù)組引用實體類。
下面的代碼片段顯示了如何定義實體:
@Entity
public class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
}
使用主鍵
每個實體必須定義至少1個字段作為主鍵岸售。即使只有1個字段践樱,仍然需要用@PrimaryKey
注解字段。此外凸丸,如果您想Room自動分配IDs給實體拷邢,則可以設(shè)置@PrimaryKey
的autoGenerate
屬性。如果實體具有復(fù)合主鍵屎慢,則可以使用@Entity
注解的primaryKeys
屬性瞭稼,如下面的代碼片段所示:
@Entity(primaryKeys = {"firstName", "lastName"})
public class User {
public String firstName;
public String lastName;
}
默認(rèn)情況下,Room使用類名作為數(shù)據(jù)庫表名腻惠。如果希望表具有不同的名稱环肘,請設(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來強制執(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實現(xiàn)氨淌。
Insert
當(dāng)您創(chuàng)建一個DAO方法并用@Insert
注解時泊愧,Room生成一個實現(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型的值删咱,這是插入項的新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ù)庫中用于修改一組實體的字段多艇。它使用每個實體的主鍵來匹配查詢。
@Dao
public interface MyDao {
@Update
public void updateUsers(User... users);
}
Delete
用于從數(shù)據(jù)庫中刪除給定參數(shù)的一系列實體像吻,它使用主鍵匹配數(shù)據(jù)庫中相應(yīng)的行
@Dao
public interface MyDao {
@Delete
public void deleteUsers(User... users);
}
Query
@Query
是DAO類中使用的主要注解峻黍。它允許您在數(shù)據(jù)庫上執(zhí)行讀/寫操作。每個@Query方法在編譯時被驗證拨匆,因此姆涩,如果存在查詢問題,則會發(fā)生編譯錯誤而不是運行時故障涮雷。
Room還驗證查詢的返回值阵面,這樣如果返回對象中字段的名稱與查詢響應(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ù)目直到運行時才知道。例如俱济,您可能希望從區(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ù)更改時自動更新蛛碌。要實現(xiàn)這一點聂喇,請在查詢方法描述中使用類型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)用程序中添加和更改特性時邀跃,你需要修改實體類以反映這些更改。當(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可以保留的已知類型矢渊。例如,如果想要持久化實例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中的實體和Dao上使用上面的定義的類型轉(zhuǎn)換器入篮。還可以限制 @TypeConverters 到不同的范圍陈瘦,包括單個實體,DAO和DAO方法潮售。