
三三要成为安卓糕手
引入
Room是一个抽象层,对SQLite进行了封装,简化了SQLite数据库的操作,让开发者能以更加对象化的方式进行数据库操作;Room解决了SQLite操作繁琐,容易产生错误的问题,让开发者能以更加对象化的方式进行数据库操作。
一:添加依赖
Room属于Jetpack中关于数据库操作的库,不属于安卓原生SDK开发包,所以需要单独添加依赖
java
// app/build.gradle
dependencies {
//添加room运行时依赖库
implementation "androidx.room:room-runtime:2.6.1"
// Room 编译时注解处理器
annotationProcessor "androidx.room:room-compiler:2.6.1"
// 可选:RxJava2 支持 (如果需要使用RxJava2的异步机制配合Room使用,需要添加这个依赖)
implementation "androidx.room:room-rxjava2:2.6.1"
// 可选:RxJava3 支持 (如果需要)
implementation "androidx.room:room-rxjava3:2.6.1"
}
二:Room 的核心组件
Room 数据库主要有三个核心组件:
-
Entity(实体类):用于表示数据库表的数据结构。
-
DAO(Data Access Object):用于定义数据库操作的方法。
-
Database(数据库类):用于创建数据库实例,并将 Entity 和 DAO 关联起来
1:Entity层
定义与作用 :Entity
代表数据库中的一张表,它是一个 Java 类,通过 @Entity
注解来标识。每个 Entity
类的实例对应表中的一行数据,类中的属性对应表中的列
java
@Entity(tableName = "user-room")
public class MyUser {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = "user_age")
private int age;
@ColumnInfo(name = "user_name")
private String name;
@ColumnInfo(name = "user_email")
private String email;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
-
Entity [ˈentəti] 实体
理解:把注解转化成编译器能够看懂的东西
-
tableName = "user-room"
指定了该实体类对应的数据库表名为user - room
。 -
@ColumnInfo(name = "user_age")
当前的字段对应我们数据库中那一列的字段 -
@PrimaryKey(autoGenerate = true)
自增主键,设置为fasle的话就需要程序员自己设置id了
2:DAO层(数据访问对象)
定义与作用 :Dao
接口定义了对数据库进行增删改查(CRUD,Create、Read、Update、Delete)操作的方法。它是应用代码与数据库之间交互的桥梁,通过定义不同的方法来执行具体的 SQL 语句或 Room 封装的操作
java
@Dao
public interface MyUserDao {
@Insert
void insertUser(MyUser user);
/**
* 通过MyUser类型直接更新数据库,Room会根据MyUser中的主键来查找对应的数据,并且更新
* @param user
*/
@Update
int updateUser(MyUser user);
/**
* 更新user-room这个表中,名字是等于name(第一个参数)的数据,把这条数据的user_age更新为age(第二个参数)
*/
@Query("update `user-room` set user_age = :age where user_name = :name")
int updateUser(String name , int age);
/**
* 通过name查询user-room表中的某个数据
* @param name
* @return
*/
@Query("select * from `user-room` where user_name = :name")
List<MyUser> getUserByName(String name);
@Delete
int delUser(MyUser user);
/**
* 通过name删除对应的用户
* @param name
* @return
*/
@Query("delete from `user-room` where user_name = :name ")
int delete(String name);
}
- 这里设置的插入方法没有返回值;有返回值的情况:当插入单个实体对象时,返回值是一个 long 类型,表示插入数据后自动生成的主键值(前提是自增主键)
3:Database层
它是一个抽象类,通过 @Database 注解来标识,通常会包含一个或者多个抽象方法),用于返回定义好的 Dao 接口实例,Room 会在运行时自动生成该抽象类的实现;(可以简单理解成一个方法对应找库中的一个表table)
java
@Database(entities = {MyUser.class} , version = 1)
public abstract class MyUserDatabase extends RoomDatabase {
public abstract MyUserDao userDao();
static final Migration MIGRATION_1_2 = new Migration(1,2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {
supportSQLiteDatabase.execSQL("alter table `user-room` add column user_email text");
}
};
}
- entities = {MyUser.class}关联一张表的名字,可以关联多张table,在花括号中填写即可;前提这张表一定是被entity标注过的
(1)版本迁移
如果要实现从版本 1 迁移到版本 5,不能直接写 Migration(1, 5)
来 "一步到位" 完成所有中间版本(1→2→3→4→5)的迁移。Room 要求必须为每一个相邻的版本跨度都定义对应的 Migration
,也就是需要分别定义:
- 从版本 1 到版本 2 的
Migration
; - 从版本 2 到版本 3 的
Migration
; - 从版本 3 到版本 4 的
Migration
; - 从版本 4 到版本 5 的
Migration
。
然后在构建数据库时,通过 addMigrations()
方法把这些所有相邻版本的 Migration
都添加进去 ,Room 会在数据库升级时,按顺序执行这些 Migration
,逐步完成从版本 1 到版本 5 的迁移。
(2)addMigration
java
MyUserDatabase userDatabase = Room.databaseBuilder(ContentProviderActivity.this
, MyUserDatabase.class, "user-database.db")
.addMigrations(MyUserDatabase.MIGRATION_1_2)
.build();
(3)数据库升级的时机
前提:创建数据库时add了对应的版本升级方法,这个迁移方法内部的具体规则需要再Database层中进行声明
如果当前版本为1,由于要给表添加新的列或者修改某个字段等等,想要升级到版本2;
那么这里就要修改为2@Database(entities = {MyUser.class} , version = 2)
,再次运行代码的时候,MyUserDatabase.MIGRATION_1_2
这个迁移规则,它会告诉 Room 如何将版本 1 的数据库转换为版本 2 的数据库,这样在数据库版本升级时,就能按照这个规则来执行相应的操作
java
static final Migration MIGRATION_1_2 = new Migration(1,2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {
supportSQLiteDatabase.execSQL("alter table `user-room` add column user_email text");
}
};
migration 信息迁移 alter 修改
效果如下,我们在Entity层中新增加的字段user_email成功添加进来了,现在就是版本2

4:创建数据库
java
MyUserDatabase userDatabase = Room.databaseBuilder(ContentProviderActivity.this
, MyUserDatabase.class, "user-database.db")
.addMigrations(MyUserDatabase.MIGRATION_1_2)
.build();
- 参数一:上下文
- 参数二:数据库的class对象
- 参数三:表示要创建的 SQLite 数据库文件的名称。
三:增删查改
1:增
java
@Insert
void insertUser(MyUser user);
java
/**
* 插入数据
*/
findViewById(R.id.btn_insert).setOnClickListener(view -> {
new Thread(new Runnable() {
@Override
public void run() {
//可以理解成操作table表的对象
MyUserDao myUserDao = userDatabase.userDao();
MyUser user = new MyUser();
user.setName(etUserName.getText().toString().trim());
user.setAge(Integer.valueOf(etUserAge.getText().toString().trim()));
myUserDao.insertUser(user);
userDatabase.close();
}
}).start();
});
数据库操作是一个非常耗时的操作,如果是在主线程操作会引起卡顿和崩溃,Room中插入数据需要指定在非主线程中进行,使用SQLite不用指定新线程,因为它内部已经帮我们处理好了
(1)插入结果
Room数据库会把我们的插入或者查询操作进行分段读写,所以就有三个文件,没有db后缀,Room也会自动帮我们转化为db文件的

为了测试,我们手动去添加后缀;并且想要整合三个文件,就需要手动close一次数据库操作


效果如下


2:查 & 改
(1)修改数据
java
/**
* 通过MyUser类型直接更新数据库,Room会根据MyUser中的主键来查找对应的数据,并且更新
* @param user
*/
@Update
int updateUser(MyUser user);
java
/**
* 更新数据
*/
findViewById(R.id.btn_update).setOnClickListener(view -> {
new Thread(new Runnable() {
@Override
public void run() {
MyUserDao myUserDao = userDatabase.userDao();
MyUser user = new MyUser();
user.setId(1);
user.setName(etUserName.getText().toString().trim());
user.setAge(Integer.valueOf(etUserAge.getText().toString().trim()));
myUserDao.updateUser(user);
//通过名字查询到对应的信息,修改这个用户的年龄
userDatabase.userDao().updateUser("lisi",100);
}
}).start();
});
(2)根据主键查找
java
/**
* 通过name查询user-room表中的某个数据
* @param name
* @return
*/
@Query("select * from `user-room` where user_name = :name")
List<MyUser> getUserByName(String name);
java
/**
* 查询数据
*/
findViewById(R.id.btn_select).setOnClickListener(view -> {
new Thread(new Runnable() {
@Override
public void run() {
MyUserDao myUserDao = userDatabase.userDao();
String queryName = etUserName.getText().toString().trim();
List<MyUser> users = myUserDao.getUserByName(queryName);
Log.i(TAG, "run:users.size" + users.size());
if(users.size() > 0){
runOnUiThread(new Runnable() {
@Override
public void run() {
//显示查询到的用户数据
MyUser user = users.get(0);
tvMessage.setText(tvMessage.getText() +
"\n" + user.getName() + ":" + user.getAge());
}
});
}
}
}).start();
});
效果如下

(3)根据字段查找
通过别的数据进行user的查找;不想通过User实体类和主键,进行查找
自定义查询规则,sql语句
java
/**
* 更新user-room这个表中,名字是等于name(第一个参数)的数据,把这条数据的user_age更新为age(第二个参数)
*/
@Query("update `user-room` set user_age = :age where user_name = :name")
int updateUser(String name , int age);
3:删
多尝试
java
@Delete
int delUser(MyUser user);
java
/**
* 通过name删除对应的用户
* @param name
* @return
*/
@Query("delete from `user-room` where user_name = :name ")
int delete(String name);
java
findViewById(R.id.btn_delete).setOnClickListener(view -> {
new Thread(new Runnable() {
@Override
public void run() {
// MyUser user = new MyUser();
// user.setId(1);
// userDatabase.userDao().delUser(user);
userDatabase.userDao().delete("wangwu");
}
}).start();
});