最全面的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中的默认值
相关文章
相关标签/搜索