【Android Jetpack】Room数据库

文章目录

  • 引入
    • Entities
    • Primary Key主键
    • 索引和唯一性
    • 对象之间的关系
    • 外键
    • 获取关联的Entity
    • 对象嵌套对象
    • Data Access Objects(DAOs)
    • 使用@Query注解的方法
        • 简单的查询
        • 带参数查询
        • 返回列的子集
        • 可被观察的查询
    • 数据库迁移
    • 用法

引入

原始的SQLite有以下两个缺点:

  • 没有编译时SQL语句的检查。尤其是当你的数据库表发生变化时,需要手动的更新相关代码,这会花费相当多的时间并且容易出错。
  • 编写大量SQL语句和Java对象之间相互转化的代码。

针对以上的缺点,Google提供了Room来解决这些问题。Room包含以下三个重要组成部分:

Database:使用注解申明一个类,注解中包含若干个Entity类,这个Database类主要负责创建数据库以及获取数据对象的。

Entities:表示每个数据库的总的一个表结构,同样也是使用注解表示,类中的每个字段都对应表中的一列。

DAO:Data Access Object的缩写,表示从从代码中直接访问数据库,屏蔽sql语句。

image-20231003213454581

和传统写数据库创建访问的代码大概形式差不多。以存储User信息为例:

@Entity
public class User {
    @PrimaryKey
    private int uid;

    @ColumnInfo(name = "first_name")
    private String firstName;

    @ColumnInfo(name = "last_name")
    private String lastName;

    // Getters and setters are ignored for brevity, 
    // but they're required for Room to work.
    //Getters和setters为了简单起见就省略了,但是对Room来说是必须的
}
@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);
}
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

在创建了上面三个文件后,就可以通过如下代码创建数据库了:

AppDatabase db = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "database-name").build();

下面详细介绍提到的各个部分:

Entities

@Entity
如果上面的User类中包含一个字段是不希望存放到数据库中的,那么可以用@Ignore注解这个字段:

@Entity
class User {
    @PrimaryKey
    public int id;

    public String firstName;
    public String lastName;

    //不需要被存放到数据库中
    @Ignore
    Bitmap picture;
}

Room持久化一个类的field必须要求这个field是可以访问的。可以把这个field设为public或者设置settergetter

@Entity
public class User {

    @PrimaryKey(autoGenerate = true)
    @NonNull
    public int id;

    @ColumnInfo(name = "user_name", defaultValue = "")
    public String userName;

    @ColumnInfo(name = "user_age")
    public int userAge;

    @ColumnInfo(name = "nick_name")
    public String nickName;

    @ColumnInfo(name = "address")
    public String address;
}

@Entity就是表示数据库中的表所映射的实体类,

@PrimaryKey表示主键,这里是id,

autoGenerate = true 是自增,

@NonNull表示不为空。

@ColumnInfo表示表中的列名,name = "user_name"表示列名的值

//可以不用写这个@ColumnInfo注解,写它主要是为了设置列名,不写则使用变量名做为列名。

Primary Key主键

每个Entity都必须定义一个field为主键,即使是这个Entity只有一个field。如果想要Room生成自动的primary key,可以使用==@PrimaryKey==的autoGenerate属性。如果Entityprimary key是多个Field的复合Key,可以向下面这样设置:

@Entity(primaryKeys = {"firstName", "lastName"})//括号内设置主键
class User {
    public String firstName;
    public String lastName;

    @Ignore
    Bitmap picture;
}

在默认情况下Room使用类名作为数据库表的名称。如果想要设置不同的名称,可以参考下面的代码,设置表名tableNameusers:

@Entity(tableName = "users")
class User {
    ...
}

和设置tableName相似,Room默认使用field的名称作为表的列名。如果想要使用不同的名称,可以通过@ColumnInfo(name = "first_name")设置,代码如下:

@Entity(tableName = "users")
class User {
    @PrimaryKey
    public int id;

    @ColumnInfo(name = "first_name")//自定义列名
    public String firstName;

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

    @Ignore
    Bitmap picture;
}

索引和唯一性

根据访问数据库的方式,你可能想对特定的field建立索引来加速你的访问。下面这段代码展示了如何在Entity中添加索引或者复合索引:

@Entity(indices = {@Index("name"),
        @Index(value = {"last_name", "address"})})

下面的代码展示了对数据库中特定field设置唯一性(这个表中的firstNamelastName不能同时相同):

@Entity(indices = {@Index(value = {"first_name", "last_name"},
        unique = true)})

对象之间的关系

SQLite是关系型数据库,那么就可以在两个对象之间建立联系。大多数ORM库允许Entity对象互相引用,但Room明确禁止了这样做。

既然不允许建立直接的关系,Room提供以外键的方式在两个Entity之间建立联系。

外键

例如,有一个Pet类需要和User类建立关系,可以通过==@ForeignKey==来达到这个目的,代码如下:

@Entity(foreignKeys = @ForeignKey(entity = User.class,
                                  parentColumns = "id",
                                  childColumns = "user_id"))
class Pet {
    @PrimaryKey
    public int petId;

    public String name;

    @ColumnInfo(name = "user_id")
    public int userId;
}

外键可以允许你定义被引用的Entity更新时发生的行为。例如你可以定义当删除User时对应的Pet类也被删除。可以在@ForeignKey中添加onDelete = CASCADE实现。

获取关联的Entity

Entity之间可能也有一对多之间的关系。比如一个User有多个Pet,通过一次查询获取多个关联的Pet

public class UserAndAllPets {
    @Embedded
    //嵌入...中
    public User user;
    @Relation(parentColumn = "id", entityColumn = "user_id")
    public List<Pet> pets;
}

@Dao
public interface UserPetDao {
    @Query("SELECT * from User")
    public List<UserAndAllPets> loadUserAndPets();
}
  • 使用 @Relation 注解的field必须是一个List或者一个Set。通常情况下, Entity 的类型是从返回类型中推断出来的,可以通过定义 entity()来定义特定的返回类型。

  • 用 @Relation 注解的field必须是public或者有public的setter。这是因为加载数据是分为两步的:① 父Entity被查询 , ②触发用 @Relation 注解的entity的查询。所以,在上面UserAndAllPets例子中,首先User所在的数据库被查询,然后触发查询Pets的查询。即Room首先出创建一个空的对象,然后设置父Entity和一个空的list。在第二次查询后,Room将会填充这个list。

对象嵌套对象

有时候需要在类里面把另一个类作为field,这时就需要使用==@Embedded==。这样就可以像查询其他列一样查询这个field
例如,User类可以包含一个field Address,代表User的地址包括所在街道、城市、州和邮编。代码如下:

class Address {
    public String street;
    public String state;
    public String city;

    @ColumnInfo(name = "post_code")
    public int postCode;
}

@Entity
class User {
    @PrimaryKey
    public int id;

    public String firstName;

    @Embedded
    public Address address;
}

在存放User的表中,包含的列名如下:id,firstName,street,state,city,post_code
Embeddedfield中也可以包含其他Embeddedfield
如果多个Embeddedfield是类型相同的,可以通过设置prefix来保证列的唯一性。

Data Access Objects(DAOs)

DAOs是数据库访问的抽象层。
Dao可以是一个接口也可以是一个抽象类。如果是抽象类,那么它可以接受一个RoomDatabase作为构造器的唯一参数。
Room不允许在主线程中防伪数据库,除非在builder里面调用allowMainThreadQueries()。因为访问数据库是耗时的,可能阻塞主线程,引起UI卡顿。

添加方便使用的方法:

Insert:使用@Insert注解的方法,Room将会生成插入的代码。

@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方法只接受一个参数,那么将返回一个long,对应着插入的rowId。如果接受多个参数,或者数组,或者集合,那么就会返回一个long的数组或者list

我们看到直接中有个参数onConflict,表示的是当插入的数据已经存在时候的处理逻辑,有三种操作逻辑:REPLACE、ABORT和IGNORE。如果不指定则默认为ABORT终止插入数据。这里我们将其指定为REPLACE替换原有数据。

Update

@Dao
public interface MyDao {
    @Update
    public void updateUsers(User... users);
}

也可以让update方法返回一个int型的整数,代表被update的行号。(根据主键搜索)

Delete

@Dao
public interface MyDao {
    @Delete
    public void deleteUsers(User... users);
}

update方法一样,也可以返回一个int型的整数,代表被delete的行号。

使用@Query注解的方法

@Query注解的方法在编译时就会被检查,如果有任何查询的问题,都会抛出编译异常,而不是等到运行以后才会发现异常。
Room也会检查查询返回值的类型,如果返回类型的字段和数据路列名存在不一致,会收到警告。如果两者完全不一致,就会产生错误。

简单的查询
@Dao
public interface MyDao {
    @Query("SELECT * FROM user")
    public User[] loadAllUsers();
}
带参数查询

下面的代码显示了如何根据年龄条件查询User信息:

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge")
    public User[] loadAllUsersOlderThan(int minAge);
}

同理,这里也会在编译时做类型检查,如果表中没有age这个列,那么就会抛出错误。
也可以穿入多个参数或一个参数作为多个约束条件查询用户:

@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);
}
返回列的子集

有时可能只需要Entity的几个field,例如只需要获取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();
}
可被观察的查询

通过和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);
}

数据库迁移

随着业务的扩展有时候需要对数据库调整一些字段。当数据库升级时,需要保存已有的数据。
Room使用Migration来实现数据库的迁移。每个Migration都指定了startVersionendVersion。在运行的时候Room运行每个Migration的migrate()方法,按正确的顺序来迁移数据库到下个版本。如果没有提供足够的迁移信息,Room会重新创建数据库,这意味着将会失去原来保存的信息。

用法

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");
    }
};
private void initDB() {
    //本地持久化数据库
    db = Room.databaseBuilder(getApplicationContext(), MyDatabase.class, "DemoDB")
            //是否允许在主线程上操作数据库,默认false。
            .allowMainThreadQueries()
            //数据库创建和打开的事件会回调到这里,可以再次操作数据库
            .addCallback(new CallBack())
            .build();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/200491.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ快速学习之WorkQueues模型、三种交换机、消息转换器(基于SpringBoot)

文章目录 前言一、WorkQueues模型消息发送消息接收能者多劳 二、交换机类型1.Fanout交换机消息发送消息接收 2.Direct交换机消息接收消息发送 3.Topic交换机消息发送消息接收 三、编程式声明队列和交换机fanout示例direct示例基于注解 四、消息转换器总结 前言 WorkQueues模型…

基于UDP的TFTP文件传输

代码&#xff1a; #include <myhead.h>//实现下载功能 int download(int cfd,struct sockaddr_in sin) {char buf[516] ""; //定义资源包char fileName[128] ""; //定义文件名printf("请输入文件名:");scanf("%s",fileName…

Rocky Linux 9.3 为 PowerPC 64 位带回云和容器镜像

RHEL 克隆版 Rocky Linux 9.3 今天发布了&#xff0c;作为红帽企业 Linux 发行版 CentOS Stream 和 Red Hat Enterprise Linux 的免费替代版本&#xff0c;现在可供下载。 Rocky Linux 9.3 是在 Rocky Linux 9.2 发布 6 个月之后发布的&#xff0c;它带回了 PowerPC 64 位 Lit…

Java核心知识点整理大全22-笔记

目录 19.1.14. CAP 一致性&#xff08;C&#xff09;&#xff1a; 可用性&#xff08;A&#xff09;&#xff1a; 分区容忍性&#xff08;P&#xff09;&#xff1a; 20. 一致性算法 20.1.1. Paxos Paxos 三种角色&#xff1a;Proposer&#xff0c;Acceptor&#xff0c;L…

MySQL修改已存在数据的字符集

在实际应用中&#xff0c;如果一开始没有正确的设置字符集&#xff0c;在运行一段时间以后&#xff0c;才发现当前字符集不能满足要求&#xff0c;需要进行调整&#xff0c;但又不想丢弃这段时间的数据&#xff0c;这个时候就需要修改字符集。 在MySQL设置默认字符集和校对规则…

【飞桨星河社区五周年线下工坊-杭州站】

? 欢迎大家参加杭州极客工坊&#xff0c;深入了解大模型前沿技术和创新应用&#xff0c;一站式体验AI原生应用开发? 精彩议程敬请期待&#xff5e; ? 时间&#xff1a;2023年12月3日 14:00-17:30 ? 地点&#xff1a;杭州西湖区花蒋路3号西溪润泽园度假酒店 ? 主题&#xf…

C++中用于动态内存的new和delete操作符

文章目录 1、动态分配内存的应用2、动态分配内存与分配给普通变量的内存有什么不同?3、C 中如何分配/释放内存4、new 操作符4.1 使用new的语法4.2 初始化内存4.3 分配内存块4.4 普通数组声明 Vs 使用new4.5 如果运行时没有足够内存可用怎么办&#xff1f; 5、delete 操作符 C/…

第二十章Java博客

如果一次只完成一件事情&#xff0c;很容易实现。但现实生活中&#xff0c;很多事情都是同时进行的。Java中为了模拟这种状态&#xff0c;引入了线程机制。简单地说&#xff0c;当程序同时完成多件事情时&#xff0c;就是所谓的多线程。多线程应用相当广泛&#xff0c;使用多线…

喜报 | 再获影响力产品奖!擎创科技实力亮相GOPS全球运维大会

10月26日-27日&#xff0c;为期两天&#xff0c;共1100余人签到的 GOPS 全球运维大会 2023 上海站已经圆满落幕。 此次会议的“2023 IT技术领导力年度颁奖典礼”中&#xff0c;擎创夏洛克AIOps数智运维管理平台凭借成熟的产品能力及广泛且优异的落地实践效益&#xff0c;得到了…

记 Doris 回归测试S3导入load_parallelism > 1

增加load_parallelism > 1的S3导入用例&#xff0c;测试导入时切分输入文件的逻辑。 这里有几个隐性的问题点&#xff08;坑&#xff09;&#xff1a; 1、导入的文件一定要大&#xff0c;一般大于128M&#xff0c;否则&#xff0c;即使设置了 load_parallelism > 1 也不…

AI搜索相关性在网站和APP上的应用

设定场景&#xff1a;您在寻找一件新衣服&#xff0c;所以在浏览最喜欢的网店。您跳到搜索栏上&#xff0c;输入您要找的东西。您期待出现什么结果&#xff1f; 高度准确、相关和即时的结果。 无论在什么网站上搜索&#xff0c;寻找什么&#xff0c;甚至在打错字或使用了错误的…

JAVA进阶之路JVM-3:JVM内存模型,运行时数据区域划分,程序计数器,虚拟机栈,本地方法栈,堆,元空间,字符串常量池

JVM内存模型 对于 Java 程序员来说&#xff0c;在虚拟机自动内存管理机制下&#xff0c;不再需要像 C/C 程序开发程序员这样为每一个操作去写对应的 delete / free 操作&#xff0c;不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序把内new存控制权利交给JVM虚拟机。一旦…

从源码解析Containerd容器启动流程

从源码解析Containerd容器启动流程 本文从源码的角度分析containerd容器启动流程以及相关功能的实现。 本篇containerd版本为v1.7.9。 更多文章访问 https://www.cyisme.top 本文从ctr run命令出发&#xff0c;分析containerd的容器启动流程。 ctr命令 查看文件cmd/ctr/comman…

高档建筑覆膜板,胶水足表面光滑

在建筑材料行业&#xff0c;选择高质量的建筑覆膜板至关重要。贵港市能强优品木业是专业从事建筑覆膜板生产销售25年的源头工厂。这家工厂一直以来致力于生产出色的覆膜板&#xff0c;以确保建筑物外观精美&#xff0c;持久耐用。 无论是商业大楼还是家庭住宅&#xff0c;外墙装…

气膜建筑助力体育场馆快速普及

传统的室内体育馆投入资金庞大&#xff0c;建设强度高&#xff0c;建设周期漫长。而气膜体育馆的出现&#xff0c;不仅显著降低了建设成本和缩短了建设周期&#xff0c;更符合节能环保的需求&#xff0c;成为推动场馆快速普及的创新建筑形式。 对于校园设施的建设而言&#xff…

可以免费使用的Axure在线版来了

Axure作为一种功能强大的原型设计工具&#xff0c;一直受到设计师的青睐。然而&#xff0c;其高昂的价格可能成为一个门槛&#xff0c;限制了一些设计师的选择。但不用担心&#xff0c;现在有一个免费的Axure在线工具即时设计&#xff0c;功能更完整&#xff0c;更划算&#xf…

『 MySQL数据库 』插入查询结果

文章目录 &#x1f39f;️ 前言&#x1f39f;️ 创建一张结构相同的表&#x1f39f;️ 表内插入查询结果&#x1f3ab; 对表内数据进行去重&#x1f3ab; 配合ORDER BY排序后以及LIMIT分页对数据进行插入 &#x1f39f;️ 前言 在MySQL数据库中不仅可以直接根据字段类型等对数据…

信而泰 SSL测试方法介绍

[本文介绍在ALPS平台上进行SSL测试的内容和方法] 什么是SSL SSL全称是Secure Sockets Layer&#xff0c;指安全套接字协议&#xff0c;为基于TCP的应用层协议提供安全连接&#xff1b;SSL介于TCP/IP协议栈的第四层和第五层之间&#xff0c;广泛用于电子商务、网上银行等。 SSL…

String你知道多少细节(含面试题)

1 字符串初始化 常见的初始化方式有以下3种 public static void main(String[] args) {String s1 "abc";System.out.println(s1);String s2 new String("abc");System.out.println(s2);char[] s3 {a,b,c};System.out.println(s3);} 【注意】 1.Strin…

【带头学C++】----- 八、C++面向对象编程 ---- 8.10 函数的默认参数

8.10 函数的默认参数 C在声明函数原型的时可为一个或者多个参数指定默认(缺省)的参数值&#xff0c;当函数调用的时候如果没有指定这个值&#xff0c;编器会自动用默认值代替。 通过为函数参数指定默认值&#xff0c;可以在调用函数时省略相应的参数&#xff0c;而该参数将使用…