【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 {@PrimaryKeyprivate 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);@Insertvoid insertAll(User... users);@Deletevoid 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 {@PrimaryKeypublic int id;public String firstName;public String lastName;//不需要被存放到数据库中@IgnoreBitmap picture;
}

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

@Entity
public class User {@PrimaryKey(autoGenerate = true)@NonNullpublic 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;@IgnoreBitmap picture;
}

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

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

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

@Entity(tableName = "users")
class User {@PrimaryKeypublic int id;@ColumnInfo(name = "first_name")//自定义列名public String firstName;@ColumnInfo(name = "last_name")public String lastName;@IgnoreBitmap 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 {@PrimaryKeypublic 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 {@PrimaryKeypublic int id;public String firstName;@Embeddedpublic 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);@Insertpublic void insertBothUsers(User user1, User user2);@Insertpublic void insertUsersAndFriends(User user, List<User> friends);
}

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

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

Update

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

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

Delete

@Dao
public interface MyDao {@Deletepublic 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) {@Overridepublic 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) {@Overridepublic 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();
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LCD屏接口与模式详解:干货超多

前言 随着时代的发展&#xff0c;现如今我们生活上已经随处可见的各种电子产品了&#xff0c;诸如手机、平板、电脑、一些其它智能单品上都有用到显示屏&#xff0c;它作为人机交互的重要桥梁之一&#xff0c;我认为它是生活中必不可少的存在&#xff0c;如果少了它&#xff0c…

【Electron】上下键切换消息

需求&#xff1a; 如图&#xff0c;需要监听上下键切换消息 Electron 注册 全局快捷键【globalShortcut】监听 在focus注册 在blur 注销 如苹果系统在使用某个软件(focus)时 右上角会有应用标题 Electron 代码&#xff1a; win.on(focus, ()>{globalShortcut.register(U…

啊哒-MISC-bugku-解题步骤

——CTF解题专栏—— 题目信息&#xff1a; 题目&#xff1a;啊哒 作者&#xff1a;第七届山东省大学生网络安全技能大赛 提示&#xff1a;无 解题附件&#xff1a; 解题思路&#xff1a; 图片的话还是老三样斧winwalk、010Editor、Stegsolve。ok直接开搞&#xff01; 解题…

基于UDP的TFTP文件传输

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

Re0: 从零实现一个置顶任意窗口的小工具

前言 话不多说&#xff0c;先上效果&#xff1a; 这里展示的是通过下拉框选择窗口&#xff0c;让窗口显示并置顶&#xff0c;其实还可以直接通过快捷键&#xff08;先鼠标点击要置顶的窗口&#xff0c;再使用CTRLSHIFTT&#xff09;&#xff0c;本文涉及到的完整代码已上传到G…

【JavaEE初阶】 HTTP 请求 (Request)详解

文章目录 &#x1f340;序言&#x1f384;认识URL&#x1f6a9;URL 基本格式&#x1f6a9;query string&#x1f6a9;关于 URL encode &#x1f334;认识 "方法" (method)&#x1f6a9;GET方法&#x1f6a9;POST 方法&#x1f6a9; GET 和 POST 的区别 &#x1f38b;…

Java后端开发——JDBC(万字详解)

Java后端开发——JDBC&#xff08;万字详解&#xff09; 今日目标 掌握JDBC的的CRUD理解JDBC中各个对象的作用掌握Druid的使用 1&#xff0c;JDBC概述 在开发中我们使用的是java语言&#xff0c;那么势必要通过java语言操作数据库中的数据。这就是接下来要学习的JDBC。 1.1 …

【Axure高保真原型】区间评分条

今天和大家分享区间评分条的原型模板&#xff0c;我们可以左右拖动移动滑块到指定位置&#xff0c;评分条上方会根据两个滑块所在的位置&#xff0c;自动计算出对应的区间范围&#xff1b;这个原型模板使用也很简单&#xff0c;只需要在里面填写区间的最大值&#xff0c;即可自…

盛最多水的容器-中等

leetcode链接 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾…

JS之Object.defineProperty方法

给对象添加属性的方法有许多&#xff0c;这次让我为大家介绍一种给对象添加属性的静态方法吧&#xff01; 语法&#xff1a;Objcet.defineProperty(对象的名称&#xff0c;“添加的键名”&#xff0c;{value&#xff1a;键值}) const obj {name:"张三",age:18}// 我…

Typora .MD笔记中本地图片批量上传到csdn (.PNG格式)(无需其他任何图床软件)

Typora .MD笔记中本地图片批量上传到csdn &#xff08;.PNG格式&#xff09;(无需其他任何图床软件) 截图软件推荐 qq 截图 快捷键 ctrlshiftA. 步骤一 设置Typora 的图片 点击文件. 点击偏好设置 ->图像 我们可以选择将图片复制到我们的文件夹中。 建议刚写好文件标题就…

C#键盘钩子(Hook)拦截器的使用

引言 键盘钩子(Hook)是一种机制&#xff0c;允许程序捕获和处理操作系统中的键盘输入。在C#中&#xff0c;我们可以使用键盘钩子来创建一个拦截器&#xff0c;用于拦截特定的键盘事件并执行自定义操作。本文将介绍如何使用C#开发一个键盘钩子拦截器&#xff0c;并给出一些示例代…

算法中的时间复杂度,空间复杂度

一、前言 算法&#xff08;Algorithm&#xff09;是指用来操作数据、解决程序问题的一组方法。对于同一个问题&#xff0c;使用不同的算法&#xff0c;也许最终得到的结果是一样的&#xff0c;但在过程中消耗的资源和时间却会有很大的区别 衡量不同算法之间的优劣主要是通过时…

Java-多线程基本知识学习总结

多线程 前言一、线程的创建1、继承Thread类2、实现Runnable接口 二、线程的生命周期三、操作线程的方法1、线程的休眠2、线程的加入3、线程的礼让4、线程的优先级 四、线程同步End 前言 Java是支持多线程的编程语言&#xff0c;所谓多线程就是程序能够同时完成多种操作。 计算…

Windows挂载NFS

ubuntu开启nfs 安装 sudo apt install nfs-kernel-server编辑 /etc/exports /data/share *(rw,no_root_squash)重启服务 sudo systemctl restart nfs-server.service验证 showmount -e localhostwindows连接NFS 选择控制面板 > 程序 > 启用或关闭 Windows 功能 添加…

理解射频中常用的史密斯圆图(Smith Chart)

理解射频中常用的史密斯圆图&#xff08;Smith Chart&#xff09; 工程中常用史密斯圆图表示射频器件端口的回波损耗 Γ \Gamma Γ. 回波损耗 回波损耗&#xff0c;又称器件端口的反射系数&#xff0c;反映了器件的端口阻抗 Z_{1L} 与传输线阻抗 Z 0 Z_0 Z0​ 之间的匹配&…

入耳耳机对耳朵有损害吗?戴哪种耳机不伤耳朵听力?

长时间佩戴入耳式耳机会对耳朵造成伤害的&#xff0c;建议佩戴骨传导耳机&#xff0c;不入耳佩戴使用更健康。 为什么推荐使用骨传导耳机&#xff0c;首先因为入耳式耳机需要塞入耳道内佩戴&#xff0c;长时间使用会使耳道内滋生细菌&#xff0c;容易引发耳道疾病&#xff0c;…

Python爬虫入门:如何设置代理IP进行网络爬取

目录 前言 一、获取代理IP 1.1 获取免费代理IP 1.2 验证代理IP 二、设置代理IP 三、使用代理IP进行网络爬取 四、总结 前言 在进行网络爬取时&#xff0c;经常会遇到一些反爬虫的措施&#xff0c;比如IP封锁、限制访问频率等。为了解决这些问题&#xff0c;我们可以使用…

软件测评中心▏软件集成测试和功能测试之间的区别和联系简析

软件集成测试是在软件开发周期的后期阶段进行的测试活动&#xff0c;旨在验证系统各个组件之间的接口和交互是否正常工作。而功能测试是一种验证软件系统是否按照需求规格说明书所规定的功能进行正确实现的测试。接下来&#xff0c;我们来分别探讨一下软件集成测试和功能测试有…

windows系统用nginx部署web应用

要在Windows系统上使用Nginx进行本地部署和运行Web应用程序&#xff0c;可以按照以下步骤进行操作&#xff1a; 1.首先下载nginx&#xff0c;需要去nginx官网&#xff1a; nginx: download 下载最新版本的&#xff1a; 2.解压缩Nginx&#xff1a;找个磁盘位置&#xff0c;新…