最全面的ROOM數據庫框架使用指南

Room屬於Google推出的JetPack組件庫中的數據庫框架.java

分析android

  1. Realm
    1. 高性能, 比SQLite快十倍
    2. 支持RxJava/Kotlin, 不支持協程
    3. 但不支持嵌套類並且要求指定默認值, 嵌套數據類我以爲不可或缺
    4. 自定義數據庫引擎, 故會要求導入JNI庫, 會致使apk體積暴增(多個架構平臺整合超過5MB)
    5. 沒法使用通常的SQLite圖形工具查看, 官方圖形工具簡陋
  2. DBFlow
    1. 主要使用函數操做數據庫, 學習成本高
    2. 原生支持數據庫加密
    3. 支持監聽數據庫
    4. 支持協程/Kotlin/RxJava
    5. 冷門, 特別是國內
  3. GreenDao
    1. 比較落伍, 配置複雜
    2. 不支持監聽數據表/Kotlin/協程等特性
    3. 已經再也不積極維護, 官方目前積極維護旗下另外開源數據庫ObjectBox
  4. ObjectBox
    1. 冷門
    2. 高性能, 比SQLite快十倍甚至超越Realm
    3. 自定義引擎致使體積龐大
    4. 沒法使用SQLite圖形工具查看
    5. 支持配置實體類來自動化的數據庫遷移
  5. ROOM
    1. 主流
    2. 學習成本相對低, 支持SQL語句
    3. 官方維護, JetPack組件中的數據庫框架
    4. 監聽數據表
    5. 支持Kotlin協程/RxJava
    6. 具有SQL語句高亮和編譯期檢查(具有AndroidStudio的支持)
    7. 使用SQLite便於多個平臺的數據庫文件傳遞(例若有些聯繫人信息就是一個SQLite文件)
    8. 因爲是SQLite能夠經過第三方框架進行數據庫加密(ROOM原生不支持)

總結:git

考慮到主流確定使用ROOM, 考慮到體積確定不會使用Realm和ObjectBox(ROOM函數也比這兩個少不少), 因爲目前Kotlin爲主以及SQL語句檢查/JetPack支持因此我是強烈推薦使用ROOM.github

特性

  1. SQL語句高亮
  2. 簡單入門
  3. 功能強大
  4. 數據庫監聽
  5. 支持Kotlin協程/RxJava/Guava

依賴

dependencies {
  def room_version = "2.2.0-rc01"

  implementation "androidx.room:room-runtime:$room_version"
  annotationProcessor "androidx.room:room-compiler:$room_version" 
  // Kotlin 使用 kapt 替代 annotationProcessor

  // 可選 - Kotlin擴展和協程支持
  implementation "androidx.room:room-ktx:$room_version"

  // 可選 - RxJava 支持
  implementation "androidx.room:room-rxjava2:$room_version"

  // 可選 - Guava 支持, including Optional and ListenableFuture
  implementation "androidx.room:room-guava:$room_version"

  // 測試幫助
  testImplementation "androidx.room:room-testing:$room_version"
}
複製代碼

Gradle配置sql

android {
  ...
    defaultConfig {
      ...
        javaCompileOptions {
          annotationProcessorOptions {
            arguments = [
              "room.schemaLocation":"$projectDir/schemas".toString(),
              "room.incremental":"true",
              "room.expandProjection":"true"]
          }
        }
    }
}
複製代碼
  • room.expandProjection: 在使用星投影時會根據函數返回類型來重寫SQL查詢語句
  • room.schemaLocation: 輸出數據庫概要, 能夠查看字段信息, 版本號, 數據庫建立語句等
  • room.incremental: 啓用 Gradle 增量註釋處理器

使用

ROOM會在建立數據庫對象時就會建立好全部已註冊的數據表結構數據庫

  1. 建立數據庫
  2. 建立操做接口
  3. 建立數據類: 通常爲JSON反序列出的data class
  4. 使用

建立數據庫數組

@Database(entities = [Book::class], version = 1)
abstract class SQLDatabase : RoomDatabase() {
    abstract fun book(): BookDao
}
複製代碼

建立操做接口bash

@Dao
interface BookDao {

    @Query("select * from Book where")
    fun qeuryAll(): List<Book>

    @Insert
    fun insert(vararg book: Book): List<Long>

    @Delete
    fun delete(book: Book): Int

    @Update
    fun update(book: Book): Int

}
複製代碼

建立數據類架構

@Entity
data class Book(
    @PrimaryKey(autoGenerate = true)
    var number: Long = 0,
    var title:String
)
複製代碼

使用併發

val db = Room.databaseBuilder(this, SQLDatabase::class.java, "drake").build()

val book = Book("活着")
db.book().insert(book)

val books = db.user().qeuryAll()
複製代碼

註解

Entity

@Entity

修飾類做爲數據表, 數據表名稱不區分大小寫

public @interface Entity {
    /** * 數據表名, 默認以類名爲表名 */
    String tableName() default "";

    /** * 索引 示例: @Entity(indices = {@Index("name"), @Index("last_name", "address")}) */
    Index[] indices() default {};

    /** * 是否繼承父類索引 */
    boolean inheritSuperIndices() default false;

    /** * 聯合主鍵 */
    String[] primaryKeys() default {};

    /** * 外鍵數組 */
    ForeignKey[] foreignKeys() default {};

    /** * 忽略字段數組 */
    String[] ignoredColumns() default {};
}
複製代碼

ROOM要求每一個數據庫序列化字段爲public訪問權限

Index

@Index

public @interface Index {
    /** * 指定索引的字段名稱 */
    String[] value();

    /** * 索引字段名稱 * index_${tableName}_${fieldName} 示例: index_Foo_bar_baz */
    String name() default "";

    /** * 惟一 */
    boolean unique() default false;
}
複製代碼

Ignore

@Ignore

被該註解修飾的字段不會被算在表結構中

Database

public @interface Database {
    /** * 指定數據庫初始化時建立數據表 */
    Class<?>[] entities();

    /** * 指定數據庫包含哪些視圖 */
    Class<?>[] views() default {};

    /** * 數據庫當前版本號 */
    int version();

    /** * 是否容許處處數據庫概要, 默認爲true. 要求配置gradle`room.schemaLocation`纔有效 */
    boolean exportSchema() default true;
}
複製代碼

PrimaryKey

@PrimaryKey

每一個數據庫要求至少設置一個主鍵字段, 即便只有一個字段的數據表

boolean autoGenerate() default false; // 主鍵自動增加
複製代碼

若是主鍵設置自動生成, 則要求必須爲Long或者Int類型.

ForeignKey

@ForeignKey

public @interface ForeignKey {
  // 引用外鍵的表的實體
  Class entity();
  
  // 要引用的外鍵列
  String[] parentColumns();
  
  // 要關聯的列
  String[] childColumns();
  
  // 當父類實體(關聯的外鍵表)從數據庫中刪除時執行的操做
  @Action int onDelete() default NO_ACTION;
  
  // 當父類實體(關聯的外鍵表)更新時執行的操做
  @Action int onUpdate() default NO_ACTION;
  
  // 在事務完成以前,是否應該推遲外鍵約束
  boolean deferred() default false;
  
  // 給onDelete,onUpdate定義的操做
  int NO_ACTION = 1; // 無動做
  int RESTRICT = 2; // 存在子健記錄時禁止刪除父鍵
  int SET_NULL = 3; // 子表刪除會致使父鍵置爲NULL 
  int SET_DEFAULT = 4; // 子表刪除會致使父鍵置爲默認值 
  int CASCADE = 5; // 父鍵刪除時子表關鍵的記錄所有刪除
  
  @IntDef({NO_ACTION, RESTRICT, SET_NULL, SET_DEFAULT, CASCADE})
  @interface Action {
    }
}
複製代碼

示例

@Entity
@ForeignKey(entity = Person::class, parentColumns = ["personId"],childColumns = ["bookId"], onDelete = ForeignKey.RESTRICT )
data class Book(
    @PrimaryKey(autoGenerate = true)
    var bookId: Int = 0,
    @ColumnInfo(defaultValue = "12") var title: String = "冰火之歌"
)
複製代碼

ColumnInfo

修飾字段做爲數據庫中的列(字段)

public @interface ColumnInfo {
    /** * 列名, 默認爲當前修飾的字段名 */
    String name() default INHERIT_FIELD_NAME;

    /** * 指定當前字段屬於Affinity類型, 通常不用 */
    @SQLiteTypeAffinity int typeAffinity() default UNDEFINED;
  
  	// 如下類型
    int UNDEFINED = 1;
    int TEXT = 2;
    int INTEGER = 3;
    int REAL = 4; //
    int BLOB = 5;

    /** * 該字段爲索引 */
    boolean index() default false;

    /** * 指定構建數據表時排列 列的順序 */
    @Collate int collate() default UNSPECIFIED;
  
    int UNSPECIFIED = 1; // 默認值, 相似於BINARY
    int BINARY = 2; // 區分大小寫
    int NOCASE = 3; // 不區分大小寫
    int RTRIM = 4; // 區分大小寫排列, 忽略尾部空格
		@RequiresApi(21)
    int LOCALIZED = 5; // 按照當前系統默認的順序
    @RequiresApi(21)
    int UNICODE = 6; // unicode順序

    /** * 當前列的默認值, 這種默認值若是改變要求處理數據庫遷移, 該參數支持SQL語句函數 */
    String defaultValue() default VALUE_UNSPECIFIED;
}
複製代碼
  1. 主要使用的參數只有index/name
  2. 並非只能修飾Entity類的字段, 非Entity的類也能夠被此註解修飾(例如用於展開投影的POJO類, 後面會提到類型投影).

Index

@Index

增長查詢速度

// 須要被添加索引的字段名
String[] value();

// 索引名稱
String name() default "";

// 表示字段在表中惟一不可重複
boolean unique() default false;
複製代碼

RawQuery

@RawQuery

該註解用於修飾參數爲SupportSQLiteQuery的Dao函數, 用於原始查詢(編譯器不會校驗SQL語句), 通常使用@Query

interface RawDao {
    @RawQuery
    fun queryBook(query:SupportSQLiteQuery): Book
 }

va; book = rawDao.queryBook(SimpleSQLiteQuery("SELECT * FROM song ORDER BY name DESC"));
複製代碼

若是要返回可觀察的對象Flow等, 則須要指定註解參數observedEntities

@RawQuery(observedEntities = [Book::class])
fun query(query:SupportSQLiteQuery): Flow<MutableList<Book>>
複製代碼

Embedded

@Embedded

若是數據表實體存在一個字段屬於另一個對象, 該字段使用此註解修飾便可讓外部類包含該類全部的字段(在數據表中).

該外部類不要求同爲Entity修飾的表結構

String prefix() default ""; 
// 前綴, 在數據表中的字段名都會被添加此前綴
複製代碼

Relation

@Relation

該註解早期只能修飾集合字段, 如今版本能夠修飾任意類型.

下面演示建立一個一對多

建立一本書

@Entity
data class Book(
    @PrimaryKey(autoGenerate = true)
    var id: Long = 0,
    var title:String = "drake"
)
複製代碼

建立一我的

@Entity
data class Person(
    @PrimaryKey(autoGenerate = true) var id: Int = 0,
    var name: String
)
複製代碼

建立一個用戶

data class User(
    @Embedded var person: Person,

    @Relation(entity = Book::class, parentColumn = "id", entityColumn = "id")
    var book: Book // 這裏表示一對一查詢, 若是是集合List<Book>則表示爲一對多
)
複製代碼
  1. entity參數通常狀況不須要指定能夠從返回值類型推斷, 若是要定義目標實體則可使用該參數
  2. User並非數據表(Entity)
  3. User必須包含Person的全部字段, 因此推薦使用Embedded註解
  4. parentColumn對應User中的字段, entityColumn對應Book中的字段(即一對多中的"多"數據表)

以後你能夠自由插入Person或者Book, 而後查詢的時候返回User

@Dao
interface UserDao {
    @Query("select * from person")
    fun find(): List<User>
}
複製代碼

能夠看到查詢SQL語句查詢的是person表, 可是函數返回類型是List<User>. 這個User即包含Peron和與Person對應的Book.

所謂對應的原則即parentColumn/entityColumn這兩個屬性,parentColumn表示

直接返回指定列

默認狀況根據類型

data class User(
    @Embedded var person: Person,

    @Relation(entity = Book::class, parentColumn = "id", entityColumn = "id")
    var book: Book // 這裏表示一對一查詢, 若是是集合List<Book>則表示爲一對多
)
複製代碼

橋接表

單獨定義一個數據表用來表示兩個數據表之間的關係

建立一個橋接表

@Entity(primaryKeys = ["personId", "bookId"])
data class LibraryRelation(
    var personId: Int,
    var bookId: Int
)
複製代碼
  1. 橋接用的數據表要求同爲主鍵

經過參數associateBy指定橋接表

data class User(
    @Embedded var person: Person,
    @Relation( entity = Book::class, parentColumn = "personId", entityColumn = "bookId", associateBy = Junction(BookCaseRef::class, parentColumn = "pId", entityColumn = "bId")
    )
    var book: List<Book>
)
複製代碼
  1. Junction中的parentColumn/entityColumn默認值爲Relation中的同名參數. 其含義爲橋接表中的字段名

完整的多對多查詢

data class User(
    @Embedded var person: Person,
    @Relation( entity = Book::class, parentColumn = "personId", entityColumn = "bookId", associateBy = Junction(BookCaseRef::class, parentColumn = "pId", entityColumn = "bId")
    )
    var book: List<Book>
)

data class BookCase(
    @Embedded var book: Book,
    @Relation( entity = Person::class, parentColumn = "bookId", entityColumn = "personId", associateBy = Junction(BookCaseRef::class, parentColumn = "bId", entityColumn = "pId")
    )
    var person: List<Person>
)
複製代碼

Transaction

Dao抽象類中能夠建立一個帶有@Transaction註解的函數, 該函數內的數據庫操做在一個事務中

通常狀況使用函數runTransaction

@Dao
abstract class UserDao {
    @Insert
    abstract fun insertPerson(person: Person): Long

    @Query("select * from Person")
    abstract fun findUser(): List<User>

    @Delete
    abstract fun delete(p: Person)

    @Transaction
    open fun multiOperation(deleteId: Int) {
        insertPerson(Person(deleteId, "nathan"))
        insertPerson(Person(deleteId, "nathan")) // 重複插入主鍵衝突致使事務失敗
    }
}
複製代碼
  1. Insert/Delete/Update修飾的函數自己就是在事務中
  2. ROOM僅容許一個事務運行, 其餘事務排隊
  3. @Tranaction要求修飾的函數不能爲final/private/abstract, 但若是該函數同時包含@Query則能夠爲abstract
  4. Query若是查詢的包含Relation註解的查詢存在多個查詢, 使用@Transaction則會多個查詢在一個事務中, 避免由於其餘的事務致使

DML

  1. 增刪改查所有以主鍵爲準, 即數據的其餘屬性能夠對應不上數據表中記錄也能夠根據主鍵刪除
  2. ROOM中DML所有由被註解修飾的抽象函數來執行
  3. DML函數中Insert能夠返回Long類型, 其餘Update/Delete返回Int類型. 或所有返回Unit.
  4. 當參數是可變時, 返回值應也是可變類型, 不然只會返回第一條記錄的值
  5. 可變類型包括 List/MutableList/Array
  6. 當進行DML進行多個數據體的操做時(例如插入多個用戶), 只要有一個不符合就所有丟棄提交
  7. 除Transaction其餘DML註解都要求爲抽象/公開/可重寫

全部DML操做都要求在被@Dao修飾的接口中定義抽象函數

@Dao
interface BookDao {

    @Query("select * from Book")
    fun find(): Flow<Book>

    @Insert
     fun insert(vararg book: Book): List<Long>

    @Delete
    fun delete(book: Book): Int

    @Update
    fun update(book: Book): Int

}
複製代碼
  1. Dao能夠是抽象類或者接口

Insert

@Insert
fun insert(book: Book): Long

@Insert
fun insert(vararg book: Book): List<Long>
複製代碼

@Insert

/** * 在插入列時出現衝突如何處理 * Use {@link OnConflictStrategy#ABORT} (默認) 回滾事務 * Use {@link OnConflictStrategy#REPLACE} 替換已存在的列 * Use {@link OnConflictStrategy#IGNORE} 保持已存在的列 */
@OnConflictStrategy
int onConflict() default OnConflictStrategy.ABORT;
複製代碼
  1. 修飾的函數返回值必須是Long類型: 表示插入的記錄主鍵值
  2. 自動產生的主鍵要求主鍵是Long或者Int類型, 同時值必須是0纔會自動生成, 若是手動指定主鍵且重複會拋出SQLiteConstraintException

Delete

@Delete

修飾的函數返回類型必須是Int: 表示刪除行索引, 從1開始

Update

@Update

根據主鍵匹配來更新數據行

返回值能夠Int, 表示更新列索引

Query

@Query

該註解只接受一個字符串參數, 該字符串屬於SQL查詢語句, 會被編譯器校驗規則和代碼高亮以及自動補全(這裏很強大).

image-20191129182540753

返回值和查詢列是否匹配會被編譯器校驗

想要引用函數參數使用:{參數名稱}

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

    @Query("SELECT * FROM user WHERE first_name LIKE :search "
           + "OR last_name LIKE :search")
    public List<User> findUserWithName(String search);
}
複製代碼

字段映射

數據表的你可能只須要幾個字段, 那麼能夠建立一個新的對象用於接收查詢的部分字段結果

user這張數據表包含的字段不少

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

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

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user")
    public List<NameTuple> loadFullName();
}
複製代碼
  • NameTuple對象能夠非Entity註解修飾
  • 名稱對應數據表中的字段名(或者使用ColumnInfo註解)

查詢參數

查詢的參數可使用集合

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    public List<NameTuple> loadUsersFromRegions(List<String> regions);
}
複製代碼

查詢結果

  • 實體對象: 查詢集合的第一個對象
  • 數組: 無結果空數組
  • 集合: 無結果空集合

實體對象未查詢到返回NULL, 集合未查詢到返回空的List非NULL.

可觀察

查詢函數能夠經過如下返回類型來註冊觀察者

  • LiveData

    • 初始化
    • 刪除
    • 更新
    • 插入
  • Flowable

    • 插入
    • 更新

查詢語句中的數據表中的行更新後會通知觀察者.

###視圖

@DatabaseView

數據庫視圖表示建立一個虛擬的數據表, 其表結構多是其餘數據表的部分列. 主要是爲了複用數據表

@DatabaseView("SELECT user.id, user.name, user.departmentId," + "department.name AS departmentName FROM user " + "INNER JOIN department ON user.departmentId = department.id")
data class UserDetail(
    var id: Long,
    var name: String?,
    var departmentId: Long,
    var departmentName: String?
)
複製代碼

註冊視圖到數據庫上視圖才能夠被建立, 以後就能夠像查詢數據表同樣查詢視圖

@Database(entities = [User::class], views = [MovieView::class], version = 1)
abstract class SQLDatabase : RoomDatabase() {
    abstract fun user(): UserDao
}
複製代碼

註解參數

public @interface DatabaseView {
    /** * 查詢語句 */
    String value() default "";

    /** * 視圖名稱, 默認爲類名 */
    String viewName() default "";
}
複製代碼

ROOM

建立數據庫訪問對象

應該遵照單例模式, 不須要訪問多個數據庫實例對象

static <T extends RoomDatabase> Builder<T> databaseBuilder(Context context, Class<T> klass, String name) // 建立一個序列化的數據庫 static <T extends RoomDatabase> Builder<T> inMemoryDatabaseBuilder(Context context, Class<T> klass) // 在內存中建立一個數據庫, 應用銷燬之後會被清除 複製代碼

ROOM默認不容許在主線程訪問數據庫, 除非使用函數allowMainThreadQueries, 可是會致使鎖住UI不推薦使用, 特別是在列表划動中查詢數據庫內容.

RoomDatabase

abstract void clearAllTables() // 清除全部數據表中的行 boolean isOpen() // 若是數據庫鏈接已經初始化打開則返回false void close() // 關閉數據庫(若是已經打開) InvalidationTracker getInvalidationTracker() Returns the invalidation tracker for this database. SupportSQLiteOpenHelper getOpenHelper() // 返回使用這個數據庫的SQLiteOpenHelper對象 Executor getQueryExecutor() Executor getTransactionExecutor() boolean inTransaction() // 若是當前線程是在事務中返回true Cursor query(String query, Object[] args) // 使用參數查詢數據庫的快捷函數 複製代碼

事務

在接口回調中的全部數據庫操做都屬於事務, 只要失敗所有回滾

public void runInTransaction(@NonNull Runnable body) public <V> V runInTransaction(@NonNull Callable<V> body) 複製代碼

RoomDatabase構造器

RoomDatabase.Builder 該構造器負責構建一個數據庫實例對象

Builder<T> addMigrations(Migration... migrations) // 添加遷移 Builder<T> allowMainThreadQueries() // 容許主線程查詢 Builder<T> createFromAsset(String databaseFilePath) // 配置room建立和打開一個預打包的數據庫, 在'assets/'目錄中 Builder<T> createFromFile(File databaseFile) // 配置room建立和打開一個預打包的數據庫 Builder<T> enableMultiInstanceInvalidation() // 設置當一個數據庫實例中數據表無效應該通知和同步其餘相同的數據庫實例, 必須兩個實例都啓用纔有效. // 這不適用內存數據庫, 只是針對不一樣數據庫文件的數據庫實例 // 默認未啓用 Builder<T> fallbackToDestructiveMigration() // 若是未找到遷移, 則容許進行破壞性的數據庫重建 Builder<T> fallbackToDestructiveMigrationFrom(int... startVersions) // 只容許指定的開始版本進行破壞性的重建數據庫 Builder<T> fallbackToDestructiveMigrationOnDowngrade() // 若是降級舊版本時遷移不可用則容許進行破壞性的遷移 Builder<T> openHelperFactory(SupportSQLiteOpenHelper.Factory factory) // 設置數據庫工廠 Builder<T> setQueryExecutor(Executor executor) // 設置異步查詢時候的線程執行器, 通常不使用該函數默認就好, 直接使用協程就行了 Builder<T> setTransactionExecutor(Executor executor) // 設置異步事務時的線程執行器 T build() // 建立數據庫 複製代碼

日誌

設置SQLite的日誌模式

Builder<T>	setJournalMode(RoomDatabase.JournalMode journalMode)
// 設置日誌模式
複製代碼

JournalMode

  • TRUNCATE 無日誌
  • WRITE_AHEAD_LOGGING 輸出日誌
  • AUTOMATIC 默認行爲, RAM低或者API16如下則無日誌

生命週期

Builder<T>	addCallback(RoomDatabase.Callback callback)
複製代碼

RoomDatabase.Callback

void onCreate(SupportSQLiteDatabase db) // 首次建立數據庫的時候 void onDestructiveMigration(SupportSQLiteDatabase db) // 破壞性遷移後 void onOpen(SupportSQLiteDatabase db) // 打開數據庫時 複製代碼

類型轉換

查詢語句中默認只容許使用基本類型及其裝箱類, 若是咱們想使用其餘類型做爲查詢條件以及字段類型則須要定義類型轉換器.

使用@TypeConverter能夠在自定義對象和數據庫序列化之間進行內容轉換

class DateConvert {

    @TypeConverter
    fun fromDate(date: Date): Long {
        return date.time
    }

    @TypeConverter
    fun toDate(date: Long): Date {
        return Date(date)
    }
}
複製代碼

TypeConverters能夠修飾

  1. 數據庫(@Database修飾)
  2. 數據體(@Entity修飾)
  3. 數據體的字段屬性, (不支持形參)
  4. 官方文檔說能夠修飾Dao可是我試驗會報錯

根據修飾不一樣所做用域也不一樣

修飾數據庫中則整個數據庫操做中Date都會通過轉換器

@Database(entities = {User.java}, version = 1)
@TypeConverters({DateConvert.class})
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}
複製代碼

shejiswuyanzubuxuyaodahsdajhdhjahdjas

DQL

ROOM支持查詢函數返回四種類型

  1. Single/Mabye/Completable/Observable/Flowable 等RxJava的被觀察者

  2. LiveData: JetPack庫中的活躍觀察者

  3. Flow: Kotlin協程中的流

  4. Cursor: SQLite在Android中最原始的查詢結果集, 此返回對象沒法監聽數據庫變化

我再也不推薦在項目中使用RxJava, 由於沒法方便實現併發而且容易產生回調地域. 這裏建議使用協程, 若是以爲沒法徹底替換RxJava, 推薦使用個人開源項目Net

除Cursor以外我列舉的全部返回類型都支持在回調中監聽數據庫查詢結果變化. 當你查詢的數據表發生變化後就會觸發觀察者(即便該變化不符合查詢結果)而後從新查詢. 而且當你的查詢涉及到的全部的數據表都會在變動時收到通知.

@Query("select * from Book")
fun find(): Flow<Array<Book>>

@Query("select * from Book")
fun find(): Observable<Array<Book>>

@Query("select * from Book")
fun find(): LiveData<Array<Book>>

@Query("select * from Book")
fun find(): LiveData<List<Book>> // List 或者 Array都是能夠的

@Query("select * from Book")
fun find(): Flow<Array<Book>>

@Query("select * from Book")
fun find(): Cursor
複製代碼

示例查詢Flow

val result = db.book().find()

GlobalScope.launch {
  result.collect { Log.d("日誌", "result = $it") }
}
複製代碼
  1. 前面提到每次變更數據表都會致使Flow再次執行, 這裏咱們可使用函數distinctUntilChanged過濾掉重複數據行爲(採用==判斷是否屬於相同數據, data class 默認支持, 其餘成員屬性須要本身從新equals函數)

    GlobalScope.launch {
      bookFlow.distinctUntilChanged().collect {
        Log.d("日誌", "result = $it")
      }
    }
    複製代碼
  2. 建議配合Net使用, 能夠作到自動跟隨生命週期以及異常處理

特性

我會不斷更新文章, 介紹跟隨版本更新的新特性

預打包的數據庫

能夠將數據庫db文件放到一個路徑(File)或者asset資產目錄下, 而後在知足遷移數據庫條件下ROOM經過複製預打包的數據庫進行重建.

當前應用數據庫版本 預打包數據庫版本 更新應用數據庫版本 遷移策略 描述
2 3 4 破壞性遷移 刪除當前應用數據庫重建版本4
2 4 4 破壞性遷移 複製預打包數據庫文件
2 3 4 手動遷移 複製預打包數據庫文件, 且運行手動遷移 3->4

建議使用DataGrip來建立SQLite文件, 後綴.sqlite或者.db本質上沒區別, 可是Android上通常使用db

展開投影

當查詢出的數據表包含不少個字段, 而我只須要其中兩個字段, 我就能夠建立一個只包含兩個字段的POJO(非Entity修飾)替代以前Entity類.

ROOM查詢返回對象不要求必定爲Entity修飾的數據表, 只要字段名對應上就能夠投影到

既然介紹到展開投影, 在此強調下用於展開投影的POJO也可使用某些註解, 例如ColumnInfo,PrimaryKey, Index

數據表和用於簡化的POJO類

@Entity
data class Book(
    @PrimaryKey(autoGenerate = true)
    var bookId: Int = 0,
    var title: String = "drake"
)

// 假設我只關注書名, 而並不想去獲取多餘的id
data class YellowBook(
    @ColumnInfo(name = "title")
    var virtual: String = "drake"
)
複製代碼

原始的查詢和使用展開投影后的查詢

// 這是本來
@Query("select * from Book")
abstract fun findBook(): List<Book>

// 這是展開投影
@Query("select * from Book")
abstract fun findBook(): List<YellowBook>
複製代碼

目標實體

DAO 註釋 @Insert@Update@Delete 如今具備一個新屬性entity

和上面介紹的展開投影相似, 依然沿用YellowBook來說解

// 本來
@Insert
fun insert(vararg book: Book): List<Long>

// 使用目標實體
@Insert(entity = Book::class)
fun insert(vararg book: YellowBook): List<Long>
複製代碼
  1. 這中間YellowBook缺乏的字段會使用默認值(ColumnInfo的defaultValues)來插入, bookId會使用自動生成的主鍵id.
  2. 這裏提到的默認值不是Kotlin參數或者字段默認值, 而是SQLite中的默認值
相關文章
相關標籤/搜索