【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语句。

和传统写数据库创建访问的代码大概形式差不多。以存储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或者设置setter和getter。
@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属性。如果Entity的primary key是多个Field的复合Key,可以向下面这样设置:
@Entity(primaryKeys = {"firstName", "lastName"})//括号内设置主键
class User {public String firstName;public String lastName;@IgnoreBitmap picture;
}
在默认情况下Room使用类名作为数据库表的名称。如果想要设置不同的名称,可以参考下面的代码,设置表名tableName为users:
@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设置唯一性(这个表中的firstName和lastName不能同时相同):
@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。
Embedded的field中也可以包含其他Embedded的field。
如果多个Embedded的field是类型相同的,可以通过设置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都指定了startVersion和endVersion。在运行的时候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();
}
相关文章:
【Android Jetpack】Room数据库
文章目录 引入EntitiesPrimary Key主键索引和唯一性对象之间的关系外键获取关联的Entity对象嵌套对象Data Access Objects(DAOs)使用Query注解的方法简单的查询带参数查询返回列的子集可被观察的查询 数据库迁移用法 引入 原始的SQLite有以下两个缺点: …...
自定义中间件
1.使用 app.use0来定义全局生效的中间件 // 导入 express 模块 const express require(express) // 创建 express的服务器实例 const app express() app.use(function(req, res, next) {// 中间件的业务逻辑 }) 2.监听 req 的 data 事件 在中间件中,需要监听 re…...
优化机器学习:解析数据归一化的重要性与应用
在机器学习中,数据归一化是一种数据预处理的技术,旨在将数据转换为相似的范围或标准化的分布。这样做的主要目的是消除不同特征之间的量纲差异或数值范围差异,以确保模型在训练时更稳定、更有效地学习特征之间的关系。 通常,机器…...
五分钟,Docker安装flink,并使用flinksql消费kafka数据
1、拉取flink镜像,创建网络 docker pull flink docker network create flink-network2、创建 jobmanager # 创建 JobManager docker run \-itd \--namejobmanager \--publish 8081:8081 \--network flink-network \--env FLINK_PROPERTIES"jobmanager.rpc.ad…...
【小聆送书第一期】让架构师的成神之路温暖你这个不景气的冬天
🌈个人主页:聆风吟 🔥系列专栏:网络奇遇记、数据结构 🔖少年有梦不应止于心动,更要付诸行动。 文章目录 📋前言 书籍一览 ⛳️书籍一⛳️书籍二⛳️书籍三⛳️书籍四⛳️书籍五⛳️书籍六⛳️书…...
网页爬虫反扒措施有哪些?
爬虫之常见的反扒 cookies 一般用requests直接请求网址的时候有时候可能会遇到反扒措施,这时候可以考虑一下加上user-agent伪装成浏览器;也可能有登录限制,这时候cookies就有用处了 浏览器中的cookie是保存我们的账号数据和访问记录&#…...
C#实现批量生成二维码
相信大家都使用过草料二维码生成器,单独生成二维码可以,但是批量生成二维码就需要收费了。既然要收费,那就自己写一个。 接口采用导入Excel文件生成二维码,首先需要读取Excel的数据,方法如下所示: /// <…...
3种在ArcGIS Pro中制作山体阴影的方法
山体阴影可以更直观的展现地貌特点,表达真实的地形,这里为大家介绍一下在ArcGIS Pro中制作山体阴影的方法,希望能对你有所帮助。 数据来源 本教程所使用的数据是从水经微图中下载的DEM数据,除了DEM数据,常见的GIS数据…...
【ChatGLM2-6B】Docker下部署及微调
【ChatGLM2-6B】小白入门及Docker下部署 一、简介1、ChatGLM2是什么2、组成部分3、相关地址 二、基于Docker安装部署1、前提2、CentOS7安装NVIDIA显卡驱动1)查看服务器版本及显卡信息2)相关依赖安装3)显卡驱动安装 2、 CentOS7安装NVIDIA-Doc…...
输入两个整数,输出它们的乘积。 ← Python 及 C++ 代码比较
【题目描述】 输入两个整数,输出它们的乘积。【Python代码】 x,ymap(int,input().split()) print(x*y) 【C代码】 #include<bits/stdc.h> using namespace std;int x,y; int main() {cin>>x>>y;cout<<x*y<<endl;return 0; }/* in:…...
C语言——从键盘输人一个表示年份的整数,判断该年份是否为闰年,并显示判断结果。
#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int year 0;printf("请输入年份:");scanf("%d",&year);if((year%4 0) && (year%100!0) || (year%400 0)){printf("%d是闰年\n",year);}else{p…...
出于隐私和安全的考虑,有时需要从谷歌删除你的个人数据,有两种方法
如果你是公众人物、企业或拥有个人品牌的人,那么拥有在线形象很重要。然而,你可能会发现,通过谷歌搜索,陌生人可以获得你的个人信息,如联系方式、地址和财务信息,这会让你感到不安。 幸运的是,…...
【同一局域网下】两台电脑之间互ping
两台电脑互ping 首先需要连接同一网咯关闭需要ping的电脑的防火墙 关闭防火墙步骤(以win11系统为例): 设置 --> 隐私和安全性 --> Windows 安全中心 打开Windows安全中心 防火墙和网络保护 --> 选择正在使用的网络 关闭 ping其他…...
【精选】Ajax技术知识点合集
Ajax技术详解 Ajax简介 Ajax 即“Asynchronous Javascript And XML”(异步 JavaScript 和 XML),是指一种创建 交互式、快速动态应用的网页开发技术,无需重新加载整个网页的情况下,能够更新页面局 部数据的技术。通过在…...
智能优化算法应用:基于水循环算法无线传感器网络(WSN)覆盖优化 - 附代码
智能优化算法应用:基于水循环算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于水循环算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.水循环算法4.实验参数设定5.算法结果6.参考文献7.…...
java-netty知识点笔记和注意事项
如何获取ctx的id 使用ctx.ctx.toString()就可以了 public void channelRead(ChannelHandlerContext ctx, Object msg) {//传来的消息包装成字节缓冲区String byteBuf (String) msg; // ByteBuf byteBuf (ByteBuf) msg;//Netty提供了字节缓冲区的toString方法ÿ…...
英伟达不同系列GPU介绍
英伟达有以下几个系列的产品线,并介绍它们的特点和主要应用领域: 1. GeForce系列(G系列): - 特点:GeForce系列是英伟达主打的消费级GPU产品线,注重提供高性能的图形处理能力和游戏特性。它们…...
C语言——I /深入理解指针(二)
一、数组名的理解 int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p &arr[0];这⾥我们使⽤ &arr[0] 的⽅式拿到了数组第⼀个元素的地址,但是其实数组名本来就是地址,⽽且 是数组⾸元素的地址,我们来做个测试。 #include <stdio.…...
MySQL使用函数和存储过程实现:向数据表快速插入大量测试数据
实现过程 1.创建表 CREATE TABLE user_info (id INT(11) NOT NULL AUTO_INCREMENT,name VARCHAR(20) DEFAULT NULL,age INT(3) DEFAULT NULL,pwd VARCHAR(20) DEFAULT NULL,phone_number VARCHAR(11) DEFAULT NULL,email VARCHAR(255) DEFAULT NULL,address VARCHAR(255) DEF…...
力扣labuladong——一刷day59
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、力扣549. 二叉树中最长的连续序列二、力扣1325. 删除给定值的叶子节点 前言 像求和、求高度这种基本的二叉树函数很容易写,有时候只要在它们的后…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
