【Android】Room数据库的使用

三三要成为安卓糕手

引入

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();
        });
相关推荐
alexhilton1 小时前
将应用迁移到Navigation 3:痛点、加班和紧急修复
android·kotlin·android jetpack
这个DBA有点耶6 小时前
NULL不是空——数据库里最反直觉的设计,90%新人踩过的坑
数据库·mysql·代码规范
杉氧7 小时前
Navigation Compose 深度实践:如何优雅地串联起你的全栈 App?
android·架构·android jetpack
这个DBA有点耶8 小时前
AI写的SQL跑崩了生产库,这锅谁背?
数据库·人工智能·程序员
镜舟科技8 小时前
Databricks 再提 LTAP,AI 时代的数据底座为何重回大一统叙事?
数据库·架构·agent
Databend9 小时前
从湖仓升级为 Agent 时代的数据控制面,Snowflake 和 Databricks 有哪些布局
大数据·数据库·agent
雨白10 小时前
指针与数组的核心机制
android
ClouGence12 小时前
SQL Server CDC 能放到 Always On 备库读吗?一文讲透原理与实践
数据库·sql server
黄林晴15 小时前
Room 3.0 正式发布!包名彻底重构,KMP 成为核心主线
android·android jetpack
三少爷的鞋15 小时前
Kotlin 协程环境下的 DCL 懒加载:别把线程时代的经验直接搬过来
android