ROOM数据库基础

584 阅读8分钟

2022.1.3 Room数据库

  • 安卓常见数据库

    • SQLIte(原生):sqlite.c执行 sql生成文件

    • Room:JetPack组件库,其实就是对SQLIte进行了二次封装

      • Room+LiveData+LifeCycle
    • ORM

    • greenDao:华为的,比较成熟

  • 查看SQLIte数据库具体信息(插件):Database Navigator

Room:

  • 概述:

    • Android Jetpack的出现统一了Android开发生态,各种三方库逐渐被官方组件所取代。

      Room也同样如此,逐渐取代竞品成为最主流的数据库ORM框架。

      这当然不仅仅因为其官方身份,更是因为其良好的开发体验,大大降低了SQLite的使用门槛。

    • 使用APT技术(编译期检查)对SQLIte进行二次封装

  • 优势

    • 相对于SQLiteOpenHelper等传统方法,使用Room操作SQLite有以下优势:

      1.编译期的SQL语法检查

      2.开发高效,避免大量模板代码

      3.API设计友好,容易理解

      4.可以LiveData关联,具备LiveData Lifecycle 的所有魅力

  • 注意要先导包:

    • 示意图:

      image-20220103190437645

  • 使用:

    • Room的使用,主要涉及以下3个组件

      Database: 访问底层数据库的入口

      Entity: 代表数据库中的表(table),一般用注解

      Data Access Object (DAO): 数据库访问者

      这三个组件的概念也出现在其他ORM框架中,有过使用经验的同学理解起来并不困难:

      通过Database获取DAO,然后通过DAO查询并获取entities,

      最终通过entities对数据库table中数据进行读写

  • 设计理念:

    • 实体加上注解--->数据表;将Dao暴露给用户,用户实现增删改查即可

    • 示意图:

      image-20220103185015800

  • 三大角色(APT注解处理器扫描注解,自动生成代码,这种比反射生成代码快得多)

    • 角色:

      • Student(Entity)
      • StudentDao:让用户面向Dao层
      • StudentDatabase
    • 示意图:

      image-20220103185425009

ROOM数据库使用

  • 导包:

    • 示意图:注意java与Kotlin是不一样的

      image-20220103190606034

  • 构造数据表:@Entity

    • 概述:使用注解进行字段标注,注意要生成对应的get/set方法
    • 代码示意

       package com.example.roomdemo01.simple1.entity;
       ​
       import androidx.room.ColumnInfo;
       import androidx.room.Entity;
       import androidx.room.PrimaryKey;
       ​
       //面向注解进行开发,完成数据表的建立
       @Entity
       public class Student {
       ​
           // 主键 SQL 唯一的       autoGenerate自增长
           @PrimaryKey(autoGenerate = true)
           private int uid;
       ​
           @ColumnInfo(name = "name")//以注解中的名字为例
           private String name;
       ​
           @ColumnInfo(name = "pwd")
           private String password;
       ​
           @ColumnInfo(name = "address")
           private int address;
       ​
           //主键是自增的。所以在构建函数中不要传进来
           public Student(String name, String password, int address) {
               this.name = name;
               this.password = password;
               this.address = address;
           }
       ​
           public int getUid() {
               return uid;
           }
       ​
           public void setUid(int uid) {
               this.uid = uid;
           }
       ​
           public String getName() {
               return name;
           }
       ​
           public void setName(String name) {
               this.name = name;
           }
       ​
           public String getPassword() {
               return password;
           }
       ​
           public void setPassword(String password) {
               this.password = password;
           }
       ​
           public int getAddress() {
               return address;
           }
       ​
           public void setAddress(int address) {
               this.address = address;
           }
       ​
           @Override
           public String toString() {
               return "Student{" +
                       "uid=" + uid +
                       ", name='" + name + ''' +
                       ", password='" + password + ''' +
                       ", address='" + address + ''' +
                       '}';
           }
       }
       ​
      
    • 构造Dao层:对数据表进行增删改查

      • 概述:

        • 使用注解将增删改查操作封装在Dao层,待会儿用户直接调用就行了
      • 代码示意:

         package com.example.roomdemo01.simple1.dao;
         ​
         import androidx.room.Dao;
         import androidx.room.Delete;
         import androidx.room.Insert;
         import androidx.room.Query;
         import androidx.room.Update;
         ​
         import com.example.roomdemo01.simple1.entity.Student;
         import com.example.roomdemo01.simple1.entity.StudentTuple;
         ​
         import java.util.List;
         ​
         @Dao
         public interface StudentDao {
         ​
             @Insert
             void insert(Student... students);//相当于一个数组
         ​
             @Delete
             void delete(Student student);//删除是根据特定条件删除的,更新同理
         ​
             @Update
             void update(Student student);
         ​
             //SQL语句才是最灵活的
             @Query("select * from Student")
             List<Student> getAll();
         ​
             // 查询一条记录
             @Query("select * from Student where name like:name")
             Student findByName(String name);
         ​
             // 数组查询 多个记录
             @Query("select * from Student where uid in(:userIds)")
             List<Student> getAllId(int[] userIds);
         ​
             // 就是查询 name pwd  给 StudentTuple 类接收
             @Query("select name,pwd from Student")
             StudentTuple getRecord();
         }
         ​
        
      • 细节:

        • 这是一个接口:会使用APT技术去生成子类完成功能
      • 后端框架历史

        • HliXXX:这个灵活
        • Mybatis:更加灵活
    • 构造DatabaseStudent:暴露出Dao给用户

      • 概述:

        • 注解说明:

          • 指定具体表,
          • 指定数据库版本(不要写-1,按道理这个是不能降级,只能升级)
          • 写好导出模式
      • 代码示意:

         package com.example.roomdemo01.simple1.db;
         ​
         import androidx.room.Database;
         import androidx.room.RoomDatabase;
         ​
         import com.example.roomdemo01.simple1.dao.StudentDao;
         import com.example.roomdemo01.simple1.entity.Student;
         ​
         // 为了养成好习惯 规则 ,要写 exportSchema = false  导出模式
         // 特色说法降级
         @Database(entities = {Student.class}, version = 1, exportSchema = false)
         public abstract class AppDataBase extends RoomDatabase {
         ​
             // 暴露dao
             public abstract StudentDao userDao();
         ​
         }
        
  • 用户:只需要面向Dao进行操作

    • 代码示意:

       package com.example.roomdemo01.simple1.ui;
       ​
       import android.os.Bundle;
       import android.util.Log;
       ​
       import androidx.appcompat.app.AppCompatActivity;
       import androidx.room.Room;
       ​
       import com.example.roomdemo01.R;
       import com.example.roomdemo01.simple1.dao.StudentDao;
       import com.example.roomdemo01.simple1.db.AppDataBase;
       import com.example.roomdemo01.simple1.entity.Student;
       import com.example.roomdemo01.simple1.entity.StudentTuple;
       ​
       import java.util.List;
       ​
       public class MainActivity extends AppCompatActivity {
       ​
           @Override
           protected void onCreate(Bundle savedInstanceState) {
               super.onCreate(savedInstanceState);
               setContentView(R.layout.activity_main);
       ​
               // 数据库的操作应该是在子线程
               DbTest t = new DbTest();
               t.start();
       ​
           }
       ​
           // 测试我们刚刚写的 三个角色Room数据库 增删改查
           public class DbTest extends Thread {
               @Override
               public void run() {
                   // 数据库的操作都在这里
                   AppDataBase DerryDB = Room.databaseBuilder(getApplicationContext(),//建造者设计模式
                           AppDataBase.class,
                           "DerryDB")//数据库名字
       ​
                           // 可以设置强制主线程,默认是让你用子线程,用子线程不会引起阻塞(耗时操作都放到子线程)
                           // .allowMainThreadQueries()
       ​
                           .build();
       ​
                   StudentDao dao = DerryDB.userDao();
       ​
                   dao.insert(new Student("Derry0", "123", 1));
                   dao.insert(new Student("Derry1", "456", 2));
                   dao.insert(new Student("Derry2", "789", 3));
                   dao.insert(new Student("Derry3", "111", 4));
       ​
                   // 查询全部数据,调用相应的函数
                   List<Student> all = dao.getAll();
                   Log.d("Derry", "run: all.toString():" + all.toString());
       ​
                   Log.i("Derry", "--------------------------");
       ​
                   // 查询名字为 Derry3 的一条数据
                   Student Derry3 = dao.findByName("Derry3");
                   Log.d("Derry", "run: Derry3.toString():" + Derry3.toString());
       ​
                   Log.i("Derry", "--------------------------");
       ​
                   // 查询 2 3 4 uid的三条数据
                   List<Student> allID = dao.getAllId(new int[]{2, 3, 4});
                   Log.d("Derry", "run: allID.toString():" + allID.toString());
       ​
                   Log.i("Derry", "--------------------------");
       ​
                   // 查询student表里面的数据  到  StudentTuple里面去
                   StudentTuple record = dao.getRecord();
                   Log.d("Derry", "run: record.toString():" + record.toString());
               }
           }
       }
      
  • 运行结果

    • 运行截图:

      image-20220103194101751

  • ROOM缺点:

    • 推荐是使用通配符 *,但是只是想去查某几个字段

    • 解决办法:再次创建一个新的类(不能加Entitiy,加了就是一张表了),只保存相差的那几个字段,然后用这个类去接收查询返回的值;

    • 就是克隆出一个小点的容器

    • 工程结构:

      image-20220103193305216

    • 代码示意:

       package com.example.roomdemo01.simple1.entity;
       ​
       ​
       import androidx.room.ColumnInfo;
       ​
       // @Entity 不能加,加了就是一张表了
       public class StudentTuple {
       ​
           @ColumnInfo(name = "name")
           public String name;
       ​
           @ColumnInfo(name="pwd")
           public String password;
       ​
           public StudentTuple(String name, String password) {
               this.name = name;
               this.password = password;
           }
       ​
           public String getName() {
               return name;
           }
       ​
           public void setName(String name) {
               this.name = name;
           }
       ​
           public String getPassword() {
               return password;
           }
       ​
           public void setPassword(String password) {
               this.password = password;
           }
       ​
           @Override
           public String toString() {
               return "StudentTuple{" +
                       "name='" + name + ''' +
                       ", password='" + password + ''' +
                       '}';
           }
       ​
       }
      

使用插件插看SQLIte数据库

  • 插件安装:

    • 搜索插件

      image-20220103195355866

    • 安装成功后,侧边栏会出现Data Browser

      image-20220103195606787

  • 运行上述代码:向SQLIte数据库添加内容

  • 导出数据库文件

    • 点击右侧侧边栏中的Device Explorer

      image-20220103195732027

    • 找到此时AS打开的项目:data/data/项目包名

      image-20220103195843678

      把这三个文件右键另存为:建立放在桌面上,待会儿好找,还要用的

    • 点击DB Browser--->绿色 +

      image-20220103200020442

    • 选择SQLIte

      image-20220103200050665

    • 将另存为的那三个文件中的DB文件添加进来:(先点击绿色,红色才出来,点红色,找DB)

      image-20220103200243668

      就是找这个蓝色框的文件,是找它另存为后的副本,因为原文件备份并且原文件隐藏的太深

    image-20220103200342290

    • 找到了点击OK

      图片.png

    • 测试链接+apply

      image-20220103200626650

    • 这个时候,数据库表就出来了

      image-20220103200716121

    • 打开SQL命令行

      image-20220103200814828

    • 结果展示:

      image-20220103201137765

Google 标准架构模式

  • 概述:MVVM+JetPack全家桶

  • MVVM:

    • View层(UI层):Activity、Fragment

    • VM层:保证View层稳定

      • LiveData
    • Model层:模型数据

    • 仓库层:数据仓库(这个是很麻烦的)

      • 有很多个数据源

        • 暴露Dao层
      • 服务器:

        • 暴露网络接口
    • 示意图:

      image-20220103204906298

LiveData如何进行关联

  • 使用APT注解处理器系统生成完整的SQL语句

  • 进入源码:

    image-20220105084509966

  • 进入invalidate函数:

    image-20220105084705746

  • 进入mInvalidationRunnable线程:判断被观察者此时是否存活

    image-20220105084935826

  • 进入mRefreshRunnable线程:当存活时将value进行post;value就是LiveData<>中的泛型类

    图片.png

    • 调用postValue(value):回调到MainActivity中,实现数据驱动UI

    图片.png

ROOM是怎么会观察数据的

  • 概述:

    • ROOM监听数据改变(invalidate函数调用时机),判断被观察者是否存活,然后在刷新UI
    • 当增加了LiveData,就会自动加一个Observer
    • 当界面不可见的时候,是不会干活的
    • 整合了LiveData+LifeCycle
  • 实在要纠结就看源码,这里面是存在弱引用的

架构模型:多状态开发模式

  • 概述

    • 存在十多个ViewModel,和一个DataBinding
    • 存在一个StateViewModel,这个东西来跟DataBinding进行双向绑定;
    • 其余的ViewModel(RequestViewModel等)拿到model层数据,交给StateViewModel;然后在通信
  • 为什么StateViewModel只有一个

    • 因为界面就只有一个,界面是拿给DataBinding进行管理的

数据库升级:

  • 细节:

    • 参数设置:

      image-20220105090444950

  • 概述:

    • 数据库升级:表结构改变(增删字段)
    • 代码位置:
  • 暴力升级:数据会丢失,慎用(删库跑路)

    • 增加字段:增加类属性,增加相应的set/get方法

      图片.png

    • 直接升级:

      image-20220105091227549

  • 稳定升级:写个方法+里面整个SQL脚本

    • 改变表结构:注意要加上注解

      image-20220105091303971

    • 修改数据库version:从1到2(就是加个1)

      image-20220105091419674

    • 设计具体调用函数:使用SQL脚本完成数据变化

      image-20220105091617093

    • 在建造者(build()函数中稳定升级):链式调用,数据库版本从1升级到2

      image-20220105091538623

测试数据库是否升级成功

  • 使用插件查看数据库导出文件

    • 导出数据库文件
    • 插件建立连接(同上)
    • 查看成功--->升级成功
  • 细节:

    • 凡是修改了数据表结构(新增了字段)就要进行数据库升级

      • 必须保证数据库表与数据库版本相同
    • 数据库一般情况是不能降级的

数据库降级

  • 非要删除一个字段,但是要保证数据库的稳定性,这个时候就需要特殊手法了(SQL四步法)

    • SQL四步法

      • 建立临时表
      • 将之前的旧表复制到临时表
      • 删除旧表
      • 将临时表名改成旧表
    • 代码:

        static final Migration MIGRATION_2_3 = new Migration(2, 3) {
               @Override
               public void migrate(@NonNull SupportSQLiteDatabase database) {
                   // SQL 四步法
       ​
                   // 1.先建立临时表
                   //  database.execSQL("create table student_temp (uid integer primary key not null,name text,pwd text,addressId)");
       ​
                   // 2.把之前表的数据(SQL语句的细节,同学们可以上网查询下)
                   // database.execSQL("insert into student_temp (uid,name,pwd,addressid) " + " select uid,name,pwd,addressid from student");
       ​
                   // 3.删除student 旧表
                   // database.execSQL("drop table student");
       ​
                   // 4.修改 临时表 为 新表 student
                   // database.execSQL("alter table student_temp rename to student");
               }
           };
       ​
      

数据库跨级:

  • 写成2--->9是可以的,但是写了2--->9,就不能写2--->8了

ROOM源码

  • APT技术

    • 导入注解处理器:注意所用的语言版本

      image-20220105120844704

    • 生成两个类

      image-20220105121304730

      原始工程结构

      image-20220105121340084

单类分析:AppDataBase_Impl

  • createOpenHelper 调用时机

    • 源代码

      图片.png

    • MainActivity中的build()函数

      image-20220105121837840

    • build()中的init()

      image-20220105121950791

    • 调用createOpenHelper 函数

      image-20220105122038762

    • 再次点击:

      image-20220105122355493

    • 跳转到build文件夹里面了

      image-20220105122439584

    • 细节:跳转到build文件夹里面了

      • 编译调用build函数后使用APT技术,扫描所有注解,自动生成代码继承程序猿编写的AppDataBase类

单类分析:StudentDao_Impl

  • 这是Dao层使用APT技术生成的代码:

    • 编译后扫描所有注解,生成代码,拼接生成SQL语句

ROOM数据库细节

  • 优势:

    • 由于架构模式决定其可以搭配LiveData进行操作
    • APT技术:自动生成代码,减少SQL语句的编写(会补充上事务)
  • 缺点:

    • API比较少,很多功能还不完善,还是需要写SQL语句