侧边栏壁纸
博主头像
Z同学博主等级

工作磨平激情前,坚持技术的热忱。 欢迎光临Z同学的技术小站。 分享最新的互联网知识。

  • 累计撰写 290 篇文章
  • 累计创建 57 个标签
  • 累计收到 98 条评论

Android Jetpack 之 room库

Z同学
2021-08-19 / 0 评论 / 1 点赞 / 1,257 阅读 / 7,930 字
温馨提示:
本文最后更新于 2021-11-18,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

前言

room 是在SQLite的基础之上,提供了一个抽象层。让我们可以更方便执行数据库的增删改查的。

所以,我们使用room任然是在使用Android 原生的SQLite数据库。

1.依赖项

最新版本依赖配置:

dependencies {
    implementation "androidx.room:room-runtime:2.3.0"
    annotationProcessor "androidx.room:room-compiler:2.3.0"

    // 如果需要扩展 可以加上下面的。普通使用可以不用下面的
	// 扩展- 支持 RxJava2 
    implementation "androidx.room:room-rxjava2:2.3.0"
    // 扩展- 支持 RxJava3
    implementation "androidx.room:room-rxjava3:2.3.0"
    // 扩展 - 支持Guava 库 包括:Optional 和 ListenableFuture
    implementation "androidx.room:room-guava:2.3.0"
    // 扩展 -支持 测试用例
    testImplementation "androidx.room:room-testing:2.3.0"
    // 扩展- 支持分页集成 -- ps 有需要分页数据查询可以了解了解 paging库
    implementation "androidx.room:room-paging:2.4.0-alpha04"
}

同时使用最新的room SDK 。建议JDK版本11以上。如果仍然使用8.0 会有警告提示:

警告: Current JDK version 1.8.0_291-b10 has a bug (https://bugs.openjdk.java.net/browse/JDK-8007720) that prevents Room from being incremental. Consider using JDK 11+ or the embedded JDK shipped with Android Studio 3.5+.
1 个警告

1.1.1版本以前的依赖配置 老版本

dependencies {
    def room_version = "1.1.1"

    implementation "android.arch.persistence.room:runtime:$room_version"
    //  如果是kotlin 使用kapt 替换 annotationProcessor
    annotationProcessor "android.arch.persistence.room:compiler:$room_version" 
    
    //下面的在2.0版本已经改了
    // 扩展- 支持 RxJava2 
    implementation "android.arch.persistence.room:rxjava2:$room_version"
    // 扩展 - 支持Guava 库 包括:Optional 和 ListenableFuture
    implementation "android.arch.persistence.room:guava:$room_version"
    //  扩展 -支持 测试用例
    testImplementation "android.arch.persistence.room:testing:$room_version"
}

2.了解

room 都是通过注解的方式来使用的。

主要分为三个模块:(都是注解)

@Entity: 定义数据库表名,列名, 主键等。 room根据该注解标注的类创建表结构。 一般放在实体类上

@Dao : 定义数据库的增删改查操作,通常定义在接口类上。 所有的SQL语句 都是采用注解的方式绑定在函数中。

@Database:必须添加在继承自RoomDatabase的抽象类上。该类主要作用是创建数据库和创建Daos(data access objects,数据访问对象)。可以理解成 整个数据库的入口和初始化操作。之后的Entity 和Dao 都是依赖于该注解标注的类对象。通过它来访问Entity和Dao

453582-20180705172114303-1455168453

3. 配置初始化

3.1 创建 Entity 类 (数据库表结构)

样例

import androidx.room.Entity;

//默认情况,将以类名做数据库表表名
@Entity()
public class MyTable {
}

//定义表名后,将按照该名称创建数据库表
@Entity(tableName = "Zinyan")
public class MyTable{

}

每一个表,都必须要有一个PrimaryKey 主键值。 所以@Entity标识的实体类,必须有一个属性是进行@PrimaryKey 标注

示例:

@PrimaryKey
private String id;

@PrimaryKey(autoGenerate = true)
private String id;

autoGenerate =true 代表该主键自增,默认为false。

默认情况下,实体类中所有的属性都将序列化后并创建相应的列。可以通过@Ignore 注解,标注该属性不进行序列化

示例:

@Ignore
private boolean isCC;

每个属性,如果存储在数据库中。建议添加完整的set和get函数。

默认情况下,列名就是由属性名决定的。但是我们也可以通过@ColumnInfo 定义新的列名。

同时关于列的一些属性配置也可以通过该注解来添加

示例:

@ColumnInfo(name = "name", index = false, defaultValue = "")
private String title;

3.1.1 columnInfo 常用字段

​ 定义列的一些常用属性,都可以通过该注解方式,并在括号里面添加指定值。

名称数据类型默认值介绍
nameString属性名定义列名
typeAffinityintUNDEFINEDSQLite独有的类型相像,定义表数存储类型的。
indexbooleanfalse是否为索引字段
collateintUNSPECIFIED列值排序规则
defaultValueString默认值设置参数的默认值,当参数没有值的时候,数据库会使用默认值

TypeAffinity 类型的选项值:使用方式:ColumnInfo.UNDEFINED 可以拿到

名称介绍
UNDEFINED默认
TEXT字符串 String
INTEGER整型数值 int 或者booleans
REAL浮点数类型 floats 或者 doubles
BLOB二进制数据

collate 类型的参数: (使用方式:ColumnInfo.UNSPECIFIED)

名称介绍
UNSPECIFIED默认值 - 效果等同BINARY
BINARY区分大小写的排序规则
NOCASE不区分大小写的排序规则
RTRIM区分大小写的排序规则,忽略结尾的空格
LOCALIZED使用系统当前语言环境的排序(Android API 21 及以上)
UNICODE使用Unicode 排序(Android API 21 及以上)

创建一个简单类的示例:

import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;

import java.util.List;

//定义表名后,将按照该名称创建数据库表
@Entity(tableName = "Zinyan")
public class MyTable {
    @PrimaryKey
    private String id;

    @ColumnInfo(name = "name")
    @NonNull //设置为非空
    private String title;

    @Ignore
    private List list;


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

3.2 创建DAO 类

示例:

package com.zinyan.demo;import androidx.room.Dao;@Daopublic interface MyDao {}

DAO 类是一个抽象接口。

@Dao 下面有相应的增删改查的注解:@Insert 添加, @Update 更新,@Delete 删除 @Query 自定义SQL语句

其中。除了@Query 外,其他的注解都是封装好了SQL语句的。只会执行该sql语句的功能。如果需要特异性功能,那么就使用@Query语句,然后填写你的SQL代码就可以了

3.2.1 insert 添加

示例代码: 全是在@Dao 注解定义的 接口类里面

//可以支持一个对象,或者多个对象的添加
@Insertvoid 
addMyTable(MyTable... myTables);

// 可以批量添加
@Insert(onConflict = OnConflictStrategy.REPLACE)
void addMuTableList(List<MyTable> list);

更多示例代码:

public interface MusicDao {
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  public void insertSongs(Song... songs);  
  @Insert
  public void insertBoth(Song song1, Song song2);
  @Insert
  public void insertAlbumWithSongs(Album album, List<Song>songs);
 }

如果,我只想添加部分数据,或者只想更新部分参数。我们可以使用entity 字段,来解决

entity 目标实体定义

//例如有一个表结构
 @Entity
 public class Playlist {
   @PrimaryKey(autoGenerate = true)
   long playlistId;
   String name;
   @Nullable
   String description
   @ColumnInfo(defaultValue = "normal")
   String category;
   @ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
   String createdTime;
   @ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
   String lastModifiedTime;
 }

public class NameAndDescription {
   String name;
   String description 
}

  @Dao
  public interface PlaylistDao {
   @Insert(entity = Playlist.class)
  // 我们可以通过entity 实体,只添加部分字段。
  //room会自动识别。将数据添加到正确的表之中。  
   public void insertNewPlaylist(NameAndDescription nameDescription);
 }

onConflice 控制

主要定义在insert 数据时 出现了错误或者冲突时,程序该如何处理。

示例:

@Insert(onConflict = OnConflictStrategy.REPLACE)
void addMuTableList(List<MyTable> list);

onConflict 总共有三种参数可选:

OnConflictStrategy.ABORT :默认值。出现冲突,撤销当前操作记录。但是已添加的数据不影响,并停止后续的操作。

OnConflictStrategy.REPLACE :出现冲突,将已有数据,进行更新。替换成最新记录。继续执行后面的语句。

OnConflictStrategy.IGNORE: 出现冲突,保留已有数据,本次操作不生效。继续执行后面的语句。

3.2.2 update 更新

示例:

@Updateint
updateTable(MyTable table); //更新一个,

@Updateint
updateTable(MyTable... table); //更新多个 

@Updateint 
updateTable(List<MyTable> table); //更新数组

它也拥有 entity 和onConflice 的属性,操作功能和效果与insert 是一样的。

例如:

@Update(onConflict = OnConflictStrategy.REPLACE)
int updateTable(MyTable table); //更新一个,

很多地方和insert 是类似的。只是相对于Insert 它定义的函数,会有返回值。

返回值通常是int 。主要是告诉我们更新成功的条目数量。

3.2.3 Delete 删除

示例

@Deletevoid 
DeleteTable(MyTable table);

@Deletevoid 
DeleteTable(List<MyTable> table); //删除多个

删除函数,有entity属性,但是没有onConflice 属性

3.2.4 query 请求

SQLite脚本逻辑 官网文档:www.sqlite.org 需要外网才能访问。 因为query更多的使用sql脚本进行的。

普通结构的删除,修改,添加我们可以使用上面已经封装好的脚本。但是如果碰见

带参数的查询,带参数的删除。等复杂维度的查询条件。我们就需要配置自己的sql了。

query注解就是来解决这个需求的。

示例:

@Query("DELETE FROM WORD") //删除数据库
void deleteWords();

@Query("DELETE FROM device WHERE id = :id") //删除指定id 的值
void deleteByDeviceId(String id);

@Query("SELECT * FROM device WHERE id = :id") // 查询 id指定id 的数据
List<Device> getDevicesById(String id);

@Query("SELECT * FROM device WHERE id = :id order by time desc") //查询指定id的数据,并按照time进行降序排序,(desc 降序,asc正序) 我们可以将查询的结果输出成
liveDataLiveData<List<Device>> getDevicesById(String id);

@Query("SELECT * FROM song WHERE id IN(:songIds)") //批量查询
List<Song>; findByIds(long[] songIds);

3.3 创建Database 类

创建完表结构,创建完SQL查询接口,我们最后就要创建一个数据库,将上面的东西封装起来,并调用了。

示例

@Database(entities = {MyTable.class, Space.class, Device.class},
        version = 1, exportSchema = false)
@TypeConverters({StringConverters.class})
public abstract class AppDataBase extends RoomDatabase {
    public abstract MyTableDao myTableDao();
    public abstract DeviceDao deviceDao();
}

有多少个类,我们就需要在entities之中写多少个class 对象,version 定义了版本号。exportSchema:设置是否导出数据库schema,默认为true,需要在build.gradle中设置。

主要就是当数据库变动之后,我们可以通过这个配置将数据库导出到指定的本地路径,进行存储。

4. 使用数据库

我们定义的各种类,当前都是一个抽象对象。我们需要实例化对象。然后才能在后面的业务之中使用它

第一步:我们实例化@Database 注解标注的类

private AppDataBaseHelper(@NonNull Context context) {
mDataBase = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class, "zinyan.db") //数据库文件名,我们可以自定义
        .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
        //Room启动时将检测version是否发生增加,如果有,那么将找到Migration去执行特定的操作。如果没有因fallbackToDestructiveMigration()。将会删除数据库并重建.
        .fallbackToDestructiveMigration()
        .build();
}

我们之后就可以直接使用mDataBase 对象获取不同表的DAO方法,执行各种SQL操作了。

4.1 Migrations 版本库迁移

我们如果在升级版本的时候,希望数据进行迁移。我们就需要创建Migration对象。

示例:

private final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {
        //例如这个 将数据库升级时 会在数据库表中 新增一个列。
        database.execSQL("ALTER TABLE 你的表名 ADD COLUMN parentIds TEXT");
    }
};

5 注意事项

1.SQLite之中表名是大小写敏感的。

2.DAO 查询事件是在子线程之中操作的。数据库操作都是在子线层中。

  1. 如果通过LiveData 观察者模式获取数据,那么当该表发生,Delete,Update,Insert 时,都会触发这个观察者对象重新查询。
    如果你这个观察者对查询涉及多个表,那么每个表的变化,都会造成观察者被触发,收到通知。
1

评论区