Android jetpack组件-Room

Room是什么?

Room 是Google为了简化旧式的SQLite操作专门提供的一个覆盖SQLite抽象层框架库
Room也是一个ORM框架,它在SQLite上提供了一个抽象层,屏蔽了部分底层的细节,使用对象对数据库进行操作,进行CRUD就像对象调用方法一样的简单。
Room 是一个对象关系映射(ORM)库。可以很容易将 SQLite 表数据转换为 Java 对象。Room 在编译时检查 SQLite 语句。

Room 为 SQLite 提供一个抽象层,以便在充分利用 SQLite 的同时,可以流畅地进行数据库访问。

作用:

实现SQLite的增、删、查、改功能。

特点:

1.使用简单(类似于Retrofit库),通过注解的方式实现相关功能。
2.拥有SQLite的所有操作功能(数据库表的所有操作、版本升级....)

Room 包含 3 个主要组件:
Room Database 数据库:底层连接的主要接入点,创建数据库就靠它了。
Data Access Objects DAO:在DAO中会有一系列对数据库进行CRUD的方法声明
Entity 实体类:是对象与数据表的对应表现,设计实体类,并最后转化为对应的数据表

Android jetpack组件-Room

主要角色说明
• Entities : 实体类,表示数据库表的数据。
• Dao : 数据操作接口,在DAO中会有一系列对数据库进行CRUD的方法声明。
• Database : 数据库持有者 & 数据库版本管理者。
• Room : 数据库的创建者 & 负责数据库版本更新的具体实现者

注释说明

1.Bean(实体)

• @Entity : 数据表的实体类。
• @PrimaryKey : 每一个实体类都需要一个唯一的标识。
• @ColumnInfo : 数据表中字段名称。
• @Ignore : 标注不需要添加到数据表中的属性。
• @Embedded : 实体类中引用其他实体类。
• @ForeignKey : 外键约束。


@Entity(tableName = "user", indices = {@Index(value = {"name"}, unique = true)})

@NonNull

@Entity(foreignKeys = @ForeignKey(entity = User.class,
        parentColumns = "id",
        childColumns = "user_id"))

Room会利用@Entity注解的类的所有字段来创建表的列,如果某些字段不希望存储的话,使用@Ignore注解该字段即可

默认情况下,Room使用类名作为表名,使用字段名作为列名。我们可以通过@Entity的tableName属性定义自己的表名,通过@ColumnInfo的name属性定义自己的列名。

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "_id")
    private int id;

Room 默认使用类名作为数据库的 Table 名称。可以通过 @Entity 的 tableName 属性设置 Table 的名称。(注意:在 SQLite 中,Table 名称是不区分大小写的。)

Room 使用字段(Filed)名称作为在数据库中的默认列名。可以通过给 Filed 添加 @ColumnInfo 注解设置列名。

主键
@PrimaryKey
每一个实体至少定义一个字段作为主键。可以将@PrimaryKey的autoGenerate属性设置为true来设置自动id。如果实体有一个复合的主键,可以使用 @Entity的primaryKeys属性来指定主键。

Android jetpack组件-Room
每个 Entity 必须设置至少一个 Field 作为主键(primary key)。即使只有 1 个 Field,也需要将其设置为主键。
有两种方法设置主键:
1.使用注解 @PrimaryKey,可以用来设置单个主键。
如果需要 Room 自动分配 IDs 给 Entity,可以设置 @PrimaryKey 的 autoGenerate 属性
2.使用注解 @Entity 的 primaryKeys 属性,可以用来设置单个主键和复合主键。

设置忽略字段(Ignore fields)
@Ignore
默认情况下,Room 为 Entity 中每个 Field 创建一列。如果在 Entity 中存在不需要持久化的 Field,可以给它们添加 @Ignore 注解。

Android jetpack组件-Room
如果子类不需要持久化父类中的 Field,使用 @Entity 的 ignoredColumns 属性更为方便。
Android jetpack组件-Room
索引和唯一性
为数据库添加索引可以加快数据的查询。在Room中可以通过@Entity的indices属性添加索引。
Android jetpack组件-Room
有时候,需要确保某个字段或者多个字段形成的组唯一。可以通过将@Index的unique属性设置为true,来确保唯一性。在下面的例子中,防止first_name和last_name这两列同时具有相同的数据
Android jetpack组件-Room
关系
SQLite是关系型数据库,你可以指定不同对象之间的关系。尽管大多数ORM类库允许对象之间互相引用,但Room明确禁止这一点。
尽管不能使用直接关系,Room仍然两个实体之间定义外键

例如,有另外一个实体Book,你可以使用@ForeignKey注解定义和User之间的关系。

Android jetpack组件-Room
外键非常有用,因为当引用的实体发生改变时,你可以指定如何处理。例如,如果@ForeignKey的onDelete属性值为CASCADE,如果删除user,所有具有相同userId的book会被全部删除。
嵌套对象
Room提供了一个注解@Embedded,允许在一个实体中嵌入另外一个实体,创建的表使用的是当前实体和嵌入实体的所有字段,所以我们可以修改上面的User实体
Android jetpack组件-Room
Android jetpack组件-Room
Android jetpack组件-Room
当一个类中嵌套多个类,并且这些类具有相同的字段,则需要调用@Embedded的属性prefix 添加一个前缀,生成的列名为前缀+列名

Android jetpack组件-Room

2.Dao(数据库操作类)

DAO(Data access object)

在 Room 持久化库中,使用数据访问对象(data access objects, DAOs)访问 App 的数据

DAO 可以是接口(interface),也可以是抽象类(abstract class)。如果是一个抽象类,可以有一个构造函数,其只接收一个 RoomDatabase 参数。在编译时,Room 为每个 DAO 创建具体实现。

注意:除非在构造器上调用 allowMainThreadQueries(),否则 Room 不支持在主线程上进行数据库访问,因为它可能会长时间锁定 UI。不过异步查询(返回 LiveData 或 Flowable 实例的查询)不受此规则约束,因为它们在需要时会在后台线程进行异步查询。

• @Dao : 标注数据库操作的类。
• @Query : 包含所有Sqlite语句操作。
• @Insert : 标注数据库的插入操作。
• @Delete : 标注数据库的删除操作。
• @Update : 标注数据库的更新操作。

数据访问对象(DAOs)

@Query 查询接受的参数是一个字符串,所以像删除或者更新我们也可以使用 @Query 注解来使用SQL语句来直接执行

@Query("delete  from user where userId = :id ")
fun deleteUserById(id:Long)

@Query("update  user set userName = :updateName where userID =  :id")
fun update(id: Long, updateName: String)

插入
当我们创建一个Dao方法,并使用@Insert注解,Room将把所有的参数在一次事物中插入到数据库中。

Android jetpack组件-Room
onConflict用来指定当发生冲突是的策略。比如将@Index的unique属性设置为true,当产生冲突时,默认情况下为OnConflictStrategy.ABORT会导致崩溃,这里设置为OnConflictStrategy.REPLACE,当发生冲突时替换老数据。关于其他的冲突策略可以阅读SQL As Understood By SQLite进行了解。

除了可以将@Insert注解的方法返回值设置为void外,还可以将方法的返回值设置为long, Long, Long[] 或者 List。如果参数是单个实体,返回long或者Long,该值是插入新条目的rowId。如果参数是集合或者多个参数时,则返回Long[]或者List

更新
使用@Update注解方法,可以使用参数实体的值更新主键值和参数实体的主键相同的行。

Android jetpack组件-Room

删除
使用@Delete注解方法,可以删除主键值和参数实体的主键相同的行

Android jetpack组件-Room

查询(Query)

@Query 是 DAO 类中的重要注解。它允许在数据库上执行读写操作。每个 @Query 方法都是在编译时验证的;因此,如果存在查询问题,将出现编译错误而不是运行时错误

在编译时,Room 还验证查询的返回值,如果返回对象中的字段名称与查询中的相应列名称不匹配,将通过以下两种方式之一告知:(在下面 3.4.3 返回列的子集 会提到)
• 如果仅仅部分 Field 名称匹配,将显示 Warning。
• 如果没有 Field 名称匹配,将显示 Error。

简单查询

Android jetpack组件-Room

@Query的值为SQL语句,可以被SQLite执行。@Query支持查询语句,删除语句和更新语句,不支持插入语句。

Android jetpack组件-Room
Room会在编译时进行检查,当代码中包含语法错误,或者表不存在,Room将在编译时出现错误信息

带参数的查询
大多数情况下,需要将参数传递到查询中以执行筛选操作,例如仅需要显示大于某一年龄的 User。这时,我们可以使用方法参数。

Android jetpack组件-Room
在编译时,Room 使用 minAge 方法参数匹配 :minAge 绑定参数。如果存在匹配错误,将出现编译错误。

还可以在查询中传递多个参数或者多次引用它们。

Android jetpack组件-Room
在查询时,传递的参数还可以是一个集合。Room 知道参数何时是一个集合,并根据提供的参数数量在运行时自动展开。

Android jetpack组件-Room
传入参数
如果我们想获取指定id的用户,该怎么办。@Query的value中支持添加绑定参数,该参数必须找到与之匹配的方法参数,并取得该方法参数的值。

Android jetpack组件-Room
传入参数
如果我们想获取指定id的用户,该怎么办。@Query的value中支持添加绑定参数,该参数必须找到与之匹配的方法参数,并取得该方法参数的值。
Android jetpack组件-Room
在这个例子中绑定参数:minAge与方法参数minAge相匹配
此外,Room还允许传入一个参数集合
Android jetpack组件-Room
返回列的子集
多数情况下,你只需要获取实体的少数几个字段。例如,你的ui可能只展示用户的名和姓,而不是每个用户的详细信息。通过只获取需要的列,可以节省资源,并且查询速度更快。

只要可以将查询的结果映射到返回的对象上,Room允许返回任何java对象。例如,可以创建如下java对象来获取用户的名和姓。

原文链接

只要结果列集合可以映射到返回的对象中,Room 允许返回任何基于 Java 的对象。例如,可以创建以下普通的 Java 对象(plain old Java-based object, POJO)来获取用户的 first name 和 last name:
注意:POJO 也可是使用 @Embedded 注解。

Android jetpack组件-Room
可观察的查询
如果希望 App 的 UI 在数据发生变化时自动更新 UI,可以在查询方法中返回一个 LiveData 类型的值。Room 会产生所有必须的代码,用于在数据库发生变化时更新这个 LivaData 对象

Android jetpack组件-Room
两种做法,一种就是用双竖杠拼接的,还有一种就是在传参的时候,把%%给拼接好
@Query(“SELECT * FROM tb_use WHERE Name LIKE ‘%’ || :name” || ‘%’)

; RxJava 的响应式查询

Room 支持返回一下 RxJava2 类型的值:
 @Query 方法:支持返回 Publisher、Flowable 和 Observable 类型的值。
 @Insert、@Update 和 @Delete 方法:Room 2.1.0 及以上版本支持返回 Completable、Single 和 Maybe 类型的值。
需要在 App 的 build.gradle 文件中添加对最新 rxjava2 版本的依赖:

Android jetpack组件-Room
Room也可以返回RxJava2的Publisher和Flowable对象。要使用这个功能需要在gradle中添加android.arch.persistence.room:rxjava2

Android jetpack组件-Room
直接返回Cursor
Room还可以直接返回Cursor对象

Android jetpack组件-Room

查询多个表

Android jetpack组件-Room

    @Query("SELECT * FROM Users LIMIT 1")
    Flowable<User> getUser();

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    Completable insertUser(User user);

    @Query("DELETE FROM Users")
    void deleteAllUsers();

    Flowable<User> getUser();

    Completable insertOrUpdateUser(User user);

原文链接

3.Database(数据库持久化)

继承数据库的创建和升级在这个类里面处理
 @Database : 标注数据库持久化的类

Android jetpack组件-Room
    defaultConfig {
       ...

        javaCompileOptions {
             ...

            annotationProcessorOptions {
                arguments += ["room.schemaLocation":
                                      "$projectDir/schemas".toString()]
            }
        }
    }

实例 代码:


@Database(entities = {RecordMarksBean.class},version = 2)
public abstract class RecordMarksBeanRoomDatabase extends RoomDatabase {

    private static final String DB_NAME = "room_sound_recorder.db";

    public abstract RecordMarksBeanDao getRecordMarksBeanDao();

    private static volatile RecordMarksBeanRoomDatabase INSTANCE;

    public static RecordMarksBeanRoomDatabase getDatabase(final Context context) {
        if (INSTANCE == null) {
            synchronized (RecordMarksBeanRoomDatabase.class) {
                if (INSTANCE == null) {

                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            RecordMarksBeanRoomDatabase.class, DB_NAME)
                            .addMigrations(MIGRATION_1_2)
                            .build();
                }
            }
        }
        return INSTANCE;
    }

    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {

            database.execSQL("ALTER TABLE room_mark_table "
                    + " ADD COLUMN  volumelist TEXT");

        }
    };
}

Android jetpack组件-Room
使用类型转换器
Room支持字符串和基本数据类型以及他们的包装类,但是如果不是基本数据类型,该如何存储呢?比如我们的User对象有个Date类型的字段birthday,我们该如何存储。Room提供了@TypeConverter可以将不可存储的类型转换为Room可以存储的类型。
Android jetpack组件-Room
上面的例子定义了两个方法,Room可以调用dateToTimestamp方法将Date转化为Long类型进行存储,也可以在查询的时候将获取的Long转换为Date对象。

为了让Room调用该转换器,使用@TypeConverters注解将转换器添加到AppDatabase上。

数据库升级
在app发布以后,我们可能会新增表或者修改原来表的结构,这就需要升级数据库。Room提供了 Migration 类用于迁移数据库,每一个 Migration 需要在构造函数里指定开始版本和结束版本。在运行时,Room会按照提供版本的顺序顺序执行每个Migration的migrate()方法,将数据库升级到最新的版本。

Android jetpack组件-Room
注意:为了使迁移逻辑正常执行,请使用完整查询,不要使用表示查询的常量。
Android jetpack组件-Room

基本使用

1.Lib引用

module 的 build.gradle 中添加以下依赖

implementation 'android.arch.persistence.room:runtime:2.2.3'
implementation "androidx.room:room-rxjava2:2.2.5"
annotationProcessor 'android.arch.persistence.room:compiler:2.2.3'
annotationProcessor 'android.arch.persistence.room:rxjava'

Android jetpack组件-Room
前面的两句是必须的,后面的部分为可选的。点击 这里 可以查看最新依赖版本号和依赖声明方法。
Android jetpack组件-Room

Room 组件

Room 有 3 个主要的组件
从上图的结构上看,至少Room Database、DAO和Entity都需要我们进行定义。官网的文档也指出,我们需要创建这三个文件,才能正式的使用Room。
Room Database:定义一个抽象类,并继承Room Database
DAO:定义一个接口类
Entity:普通Java Bean类
• Database:包含数据库持有者,并作为与 App 持久关联数据的底层连接的主要访问点。
用 @Database 注解的类应满足以下条件:
• 是一个继承至 RoomDatabase 的抽象类。
• 在注解中包含与数据库相关联的实体列表。
• 包含一个具有 0 个参数的抽象方法,并返回用 @Dao 注解的类。
在运行时,您可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 获取 Database 实例。
• Entity:表示数据库内的表(Table)。
• DAO:包含用于访问数据库的方法。

Room 各组件间关系
Room 的大致使用方法如下:
• App 通过 Room 的 Database 获取与数据库相关的数据库访问对象(DAO)。
• 然后,App 使用 DAO 从数据库中获取 Entity,并且将 Entity 的变化保存到数据库中。
• 最后,APP 使用 Entity 获取和设置数据库中表的数据
room分为三部分:
Entity:数据库实体,系统根据Entity类创建数据库,里面规定了PrimaryKey,列名、表名等数据库必备设定
Dao:Database access object:定义了一些操作数据库的操作,比如说增删改查
Database:可以认为是完整的数据库,完整的数据库包括数据库信息和数据库操作,也就是Entity和Dao

值得注意的是,如果Entity()的参数为空,系统在创建数据库时,会把类名作为数据库的表名,如果要自定义表名,可以直接在Entity()里输入参数:
@Entity(tableName = “yourTableName”)

Android jetpack组件-Room
数据库的元素
主键:每一个实体至少定义一个字段作为主键。可以将@PrimaryKey的autoGenerate属性设置为true来设置自动id。如果实体有一个复合的主键,可以使用 @Entity的primaryKeys属性来指定主键。

创建Entity类
创建Dao类
创建database类

我们通过@Database()来标记这个类为database类,在它的参数中我们可以定义:
 entities:传入所有Entity的class对象;
 version:数据库版本号。
 exportSchema:设置是否导出数据库schema,默认为true,需要在build.gradle中设置:
当数据库发生改变时,数据库版本号会接着改变,以便更好的进行备份恢复,这里我们用不到,就随便设计一个值

要求:
 必须是abstract类而且的extends RoomDatabase。
 必须在类头的注释中包含与数据库关联的实体列表(Entity对应的类)。
 包含一个具有0个参数的抽象方法,并返回用@Dao注解的类。

使用数据库
我们终于能够操作我们的数据库了。但是所有的操作必须在后台线程中完成。你可以通过使用AsyncTask,Thread,Handler,RxJava或其它方式来完成

; 数据库升级

Android jetpack组件-Room
Android jetpack组件-Room

使用Room引用复杂数据
Room提供了功能支持基数数据类型和包装类型之间的转变, 但是并不允许实体间的对象引用

使用类型转换器

有时候, 应用需要使用自定义数据类型, 该数据类型的值将保存在数据库列中. 要添加这种自定义类

Android jetpack组件-Room
接下来, 添加@TypeConverters注解到AppDatabbase类上, 之后Room就能够在AppDatabase中定义的每一个实体和DAO上使用这个转换器.

Android jetpack组件-Room

@Dao
public interface UserDao {

    @Query("SELECT * FROM user")
    List<User> getAllUsers();

    @Query("SELECT * FROM user")
    LiveData<List<User>> getAllUser();

    @Query("SELECT * FROM user WHERE id=:id")
    User getUser(int id);

    @Query("SELECT * FROM user WHERE name=:name")
    User getUser(String name);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    List<Long> insert(User... users);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    Long insert(User user);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    List<Long> insert(List<User> userLists);

    @Update
    int update(User... users);

    @Update()
    int updateAll(User... user);

    @Update()
    int updateAll(List<User> user);

    @Delete
    int delete(User user);

    @Delete
    int deleteAll(List<User> users);

    @Delete
    int deleteAll(User... users);

    @Query("SELECT * from user where name = :name LIMIT 1")
    Flowable<User> getUserByName(String name);

    @Query("SELECT * FROM book "
            + "INNER JOIN user ON user.id = book.user_id "
            + "WHERE   user.id = :userName")
    List<Book> findBooksByUserName(String userName);

}

下面是 Android Room 的官方介绍文档:

Room Persistence LibraryRoom 库的简单介绍)

Save data in a local database using Room(Room 的使用指南)

Android Room with a View – Java(Room 的使用实例)

Android jetpack组件-Room

; 使用ROOM无法查看到数据库表结构原因分析

使用room生成的数据库文件有三个:.db文件、.db-shm文件、.db-wal文件。

  1. db-wal:从3.7.0版本开始,SQLite支持一种新的事务控制机制,称为”写前日志”或”WAL”。当数据库处于WAL模式时,到该数据库的所有连接都必须使用WAL。特定的数据库将使用回滚日志或WAL,但不能同时使用两者。WAL始终位于与数据库文件相同的目录中,并且具有与数据库文件相同的名称,但是附加了字符串”-wal”。
  2. db-shm:从概念上讲,wal-index是共享内存,尽管当前的VFS实现为wal-index使用一个映射文件。映射文件位于与数据库相同的目录中,并具有与数据库相同的名称,后面附加了”-shm”后缀。因为WAL索引是共享内存,所以当客户机位于不同的机器上时,SQLite不支持网络文件系统上的journal_mode=WAL。数据库的所有用户必须能够共享相同的内存

所有以当要打开对应的数据库查看表结构的时候要同时把.db、.db-shm、.db-wal三个文件放在同一文件夹下,然后再数据库查看软件中导入对应的.db文件就可以查看数据库

可参考资料:
Android Jetpack Room的详细教程

Original: https://blog.csdn.net/FannyZhong/article/details/123372940
Author: JeeZhong
Title: Android jetpack组件-Room

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/815590/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球