前言

Google在没有推出Room组件之前,如果我们想要操作数据库,android是采用sqlite作为数据库存储。有使用过sqlite的读者应该十分清楚,sqlite的使用相当的复杂,而且代码写起来既繁琐又容易出错。因此,各大开源社区便推出了各种ORM库来简化sqlite的操作。而作为Google的亲儿子Room也是对sqlite的一层抽象。它使用了APT机制,通过注解的方式在编译阶段生成sqlite代码,使开发者不需要写冗杂的sqlite代码,提高开发效率。

基本使用

想要了解一个东西的用法,我们首先要会用它。sqlite的用法这里不多介绍了,如果不会使用的读者可以参考下面这边博客:android使用SQLite。sqlite的用法是繁琐了点,大家多写几遍就熟悉了。
我们重点看下room的用法。

从上面这幅图中可以清楚的看到,整个room的操作流程:room对象持有了dao对象的引用,dao对象则持有了entity对象的引用,entity对象则被ui(一般是activity、fragment)持有。说的通俗一点,就是Room类中有一个dao类型的成员变量,而dao类中则有一个entity类型的成员变量,ui中则有一个entity的成员变量。
那么room、dao、entity都是些什么东西呢?在代码中又是怎么表现出来的呢?

  • Entity:一个Entity 对应于数据库中的一张表。Entity 类是 Sqlite 表结构
    对 Java 类的映射,在Java 中可以被看作一个Model 类。也就是Entity就是一个java的model类,只不过这个类被加入了一个Entity注解。
  • Dao:其实就是一个接口,只不过这个接口被Dao注解注释着而已,接口里面的方法则对应着sql中的增删改查等方法。
  • Room:一个抽象类,被Database注解注释着,里面持有着真正的room对象,底层原理其实是初始化sqlite对象。

多说无益,我们直接上代码演示:

  • 添加gradle依赖
dependencies {def room_version = "2.4.1"//目前最新的稳定版本implementation "androidx.room:room-runtime:$room_version"annotationProcessor "androidx.room:room-compiler:$room_version"
}
  • Entity层
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;// 注意:升级的字段  1-2个升级开始@ColumnInfo(name = "flag")private int flag;public int getFlag() {return flag;}public void setFlag(int flag) {this.flag = flag;}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;}@Overridepublic String toString() {return "Student{" +"uid=" + uid +", name='" + name + '\'' +", password='" + password + '\'' +", address='" + address + '\'' +'}';}
}
package com.example.roomdemo01.simple1.entity;import androidx.room.ColumnInfo;// @Entity 不能加,加了就是一张表了
public class StudentTuple {@ColumnInfo(name = "name")//需要添加ColumnInfo注解,这样才能知道数据库的字段映射到model的哪个属性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;}@Overridepublic String toString() {return "StudentTuple{" +"name='" + name + '\'' +", password='" + password + '\'' +'}';}}
  • @Entity标签用于将Student类与Room中的数据表对应起来。tableName属性可以为数据表设置表名,若不设置,则表名与类名相同,也就是说model类加了Entity标签就是一张表

  • @PrimaryKey标签用于指定该字段作为表的主键。

  • @ColumnInfo标签可用于设置该字段存储在数据库表中的名字,并指定字段的类型。

  • @Ignore标签用来告诉Room忽略该字段或方法。Room不会持久化被@Ignore标签标记过的字段的数据

  • 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 {@Insertvoid insert(Student... students);@Deletevoid delete(Student student);@Updatevoid update(Student student);@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字段,如果给Student类接收,那么address字段就会为null;//如果不想出现这种情况,我们可以新建一个普通的Model类,切记该类不能添加Entity注解,因为添加了就是一张表了@Query("select name,pwd from Student")StudentTuple getRecord();
}
  • DataBase层
// 为了养成好习惯 规则 ,要写 exportSchema = false ,因为在升级过程中会记录所有的历史版本信息,,因为内部要记录升级的所有副本
//使用抽象类是为了能够在编译时期使用apt生成真正的类
@Database(entities = {Student.class}, version = 1, exportSchema = false)
public abstract class AppDataBase extends RoomDatabase {private static final AppDataBase databaseInstance;
private static final String DATABASE_NAME = "my_db"public static synchronized AppDataBase getInstance(Context context) {if (databaseInstance == null) {databaseInstance = Room.databaseBuilder(context.getApplicationContext(),AppDataBase.class, DATABASE_NAME)//从assets/database目录下读取students.db//.createFromAsset("databases/students.db")// 可以设置强制主线程,默认是让你用子线程// .allowMainThreadQueries().build();}return databaseInstance;}// 暴露daopublic abstract StudentDao userDao();}
  • @Database标签用来告诉系统这是Room数据库对象。entity属性用于指定该数据库有哪些表,若需要建立多张表,则表名以逗号隔开。version属性用于指定数据库版本号,后面数据库的升级正是根据版本号进行判断的。

  • ui层

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 {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 数据库的操作应该是在子线程DbTest t = new DbTest();t.start();}// 测试我们刚刚写的 三个角色Room数据库 增删改查public class DbTest extends Thread {@Overridepublic void run() {// 数据库的操作都在这里AppDataBase MyDB = AppDataBase.getInstance(MainActivity.this);StudentDao dao = MyDB.userDao();dao.insert(new Student("Studnet1", "123", 1));dao.insert(new Student("Studnet2", "456", 2));dao.insert(new Student("Studnet3", "789", 3));dao.insert(new Student("Studnet4", "111", 4));// 查询全部数据List<Student> all = dao.getAll();Log.d("DbTest", "run: all.toString():" + all.toString());Log.i("DeDbTest", "--------------------------");// 查询名字为 Studnet3 的一条数据Student stu = dao.findByName("Studnet3");Log.d("DbTest", "run: stu.toString():" + stu.toString());Log.i("DbTest", "--------------------------");// 查询 2 3 4 uid的三条数据List<Student> allID = dao.getAllId(new int[]{2, 3, 4});Log.d("DbTest", "run: allID.toString():" + allID.toString());Log.i("DbTest", "--------------------------");// 查询student表里面的数据  到  StudentTuple里面去StudentTuple record = dao.getRecord();Log.d("DbTest", "run: record.toString():" + record.toString());}}
}

是不是很像后端中spring的开发方式,分层以及大量的注解。

运行结果如下图所示:

高级使用

现在,我们知道了room的基本用法了。这里又个疑问,Google为什么要大费周章的弄个room呢?如果仅仅只是为了对sqlite做一层封装那么个人感觉有点大材小用了。其实,Room组件最大的魅力在于能够于livedata组件一起使用。LiveData组件大家知道:它是整个jetpack的核心组件,只要数据有一点改变那么ui层就能立马得知,从而改变ui。这可就和Google推荐的开发模式:jetpack+kotlin+mvvm遥相呼应了。

下面,来演示下LiveData如何与Room结合使用,其实用法不难:

 // 在dao层中使用 LiveData 关联 Room,这样从数据库中拿到的数据便可以映射到LiveData中@Query("select * from Student order by uid")LiveData<List<Student>> getAllLiveDataStudent();

然后,我们在ui层中对LiveData进行观察

package com.example.roomdemo02;import android.os.Bundle;
import android.widget.ListView;import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;import java.util.List;public class MainActivity extends AppCompatActivity {ListView listView;StudentViewModel studentViewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);listView = findViewById(R.id.listView);studentViewModel = ViewModelProviders.of(this).get(StudentViewModel.class);// 观察者,只要页面在前台,只要数据改变,用户就可以肉眼看到这种变化studentViewModel.getAllLiveDataStudent().observe(this, new Observer<List<Student>>() {@Overridepublic void onChanged(List<Student> students) {// 更新UIlistView.setAdapter(new GoodsAdapter(MainActivity.this, students));}});// 模拟 仓库new Thread()  {@Overridepublic void run() {super.run();try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}// 默认给数据库ROOM增加数据for (int i = 0; i < 50; i++) {studentViewModel.insert(new Student("Student", "123", 1));}}}.start();// 模拟仓库 数据库 数据被修改了,一旦数据库被修改,那么数据会驱动UI的发生改变new Thread() {@Overridepublic void run() {for (int i = 0; i < 50; i++) {try {Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();}studentViewModel.update(new Student(6, "Student" + i, "123", 1));}}}.start();}
}

注意:LiveData通常与ViewModel一起使用,ViewModel是用于存放数据的,因此可以将数据库放在ViewModel中进行实例化。但数据库的实例化需要用到Context,而ViewModel中最好不要传入Context,因此,我们不宜直接使用ViewModel,而应该用它的子类AndroidViewModel,并且在viemodel的构造函数中传入Application类型的上下文。

Room数据库版本升级

import android.content.Context;import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;// exportSchema = false 尽量写,内部需要检测,如果没有写,会抛出异常,因为内部要记录升级的所有副本
@Database(entities = {Student.class}, version = 2, exportSchema = false)
public abstract class AppDatabase  extends RoomDatabase {private static AppDatabase instance;public static synchronized AppDatabase getInstance(Context context) {if (instance == null) {instance = Room.databaseBuilder(context.getApplicationContext(),AppDatabase.class, "my_db")// 可以强制在主线程运行数据库操作.allowMainThreadQueries()// 暴力升级 不管三七二十一 强制执行(数据会丢失)(慎用)// .fallbackToDestructiveMigration()// 稳定的方式升级.addMigrations(MIGRATION_1_2).build();}return instance;}public abstract StudentDao studentDao();// 下面是稳定升级的方式static final Migration MIGRATION_1_2 = new Migration(1, 2) {@Overridepublic void migrate(SupportSQLiteDatabase database) {// 在这里用SQL脚本完成数据的变化database.execSQL("alter table student add column flag integer not null default 1");}};// ROOM 是不能降级的,我非要删除一个字段,却要保证数据的稳定性,这个是特殊情况// 特殊手法降级static final Migration MIGRATION_2_3 = new Migration(2, 3) {@Overridepublic 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");}};
}

源码分析

首先,我们需要明确我们的疑惑是什么?笔者在学习room时的疑惑有下面几点:

  1. room是如何封装sqlite?
  2. Dao层中哪些带注解的方法又是如何被调用的呢?

接下来,我们就带着这两个疑惑来看源码。首先,我们从Room的初始化方法开始看起。我们知道Room采用了建造者模式

public T build() {//noinspection ConstantConditionsif (mContext == null) {throw new IllegalArgumentException("Cannot provide null context for the database.");}//noinspection ConstantConditionsif (mDatabaseClass == null) {throw new IllegalArgumentException("Must provide an abstract class that"+ " extends RoomDatabase");}if (mQueryExecutor == null && mTransactionExecutor == null) {mQueryExecutor = mTransactionExecutor = ArchTaskExecutor.getIOThreadExecutor();} else if (mQueryExecutor != null && mTransactionExecutor == null) {mTransactionExecutor = mQueryExecutor;} else if (mQueryExecutor == null && mTransactionExecutor != null) {mQueryExecutor = mTransactionExecutor;}if (mMigrationStartAndEndVersions != null && mMigrationsNotRequiredFrom != null) {for (Integer version : mMigrationStartAndEndVersions) {if (mMigrationsNotRequiredFrom.contains(version)) {throw new IllegalArgumentException("Inconsistency detected. A Migration was supplied to "+ "addMigration(Migration... migrations) that has a start "+ "or end version equal to a start version supplied to "+ "fallbackToDestructiveMigrationFrom(int... "+ "startVersions). Start version: "+ version);}}}if (mFactory == null) {mFactory = new FrameworkSQLiteOpenHelperFactory();}if (mCopyFromAssetPath != null || mCopyFromFile != null) {if (mName == null) {throw new IllegalArgumentException("Cannot create from asset or file for an "+ "in-memory database.");}if (mCopyFromAssetPath != null && mCopyFromFile != null) {throw new IllegalArgumentException("Both createFromAsset() and "+ "createFromFile() was called on this Builder but the database can "+ "only be created using one of the two configurations.");}mFactory = new SQLiteCopyOpenHelperFactory(mCopyFromAssetPath, mCopyFromFile,mFactory);}
//上面这些都是些支线逻辑,我们不需要看DatabaseConfiguration configuration =new DatabaseConfiguration(mContext,mName,mFactory,mMigrationContainer,mCallbacks,mAllowMainThreadQueries,mJournalMode.resolve(mContext),mQueryExecutor,mTransactionExecutor,mMultiInstanceInvalidation,mRequireMigration,mAllowDestructiveMigrationOnDowngrade,mMigrationsNotRequiredFrom,mCopyFromAssetPath,mCopyFromFile);T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);db.init(configuration);return db;}

上面的build方法中重点在于最后一句init方法,我们进入init方法看下究竟做了什么事情。

点击去之后,发现居然是个抽象方法,那么我们进入它是实现类看下,这个AppDatabase_Impl其实就是APT生成的类

看到这里大家应该都明白了吧!其实room底层也是调用了sqlite来创建数据库的。我们看下createAllTables方法是怎么被调的。

createAllTables被RoomOpenHelper的onCreate方法调用,而RoomOpenHelper的onCreate方法则是被OpenHelper类的onCreate方法调用,这个OpenHelper类则是在FrameworkSQLiteOpenHelper类的构造方法中被初始化,而FrameworkSQLiteOpenHelper则是在前面的build方法的时候被初始化的。

至此,整个room的初始化逻辑链路全部连接起来了。
至于,第二点则跟简单了,APT会生成一个Impl类,此例是StudentDao_Impl类,我们调用的其实就是该类的对应方法。

最后

感谢各位读者耐心读到最后,谢谢!!!

jetpack组件之Room原理分析相关推荐

  1. Android Jetpack组件ViewModel基本使用和原理分析

    本文整体流程:首先要知道什么是 ViewModel,然后演示一个例子,来看看 ViewModel 是怎么使用的,接着提出问题为什么是这样的,最后读源码来解释原因! 1.什么是ViewModel 1.1 ...

  2. Java 线程同步组件 CountDownLatch 与 CyclicBarrier 原理分析

    1.简介 在分析完AbstractQueuedSynchronizer(以下简称 AQS)和ReentrantLock的原理后,本文将分析 java.util.concurrent 包下的两个线程同步 ...

  3. Android应用程序组件Content Provider在应用程序之间共享数据的原理分析(1)

             在Android系统中,不同的应用程序是不能直接读写对方的数据文件的,如果它们想共享数据的话,只能通过Content Provider组件来实现.那么,Content Provide ...

  4. 可视化拖拽组件库一些技术要点原理分析(三)

    本文是可视化拖拽系列的第三篇,之前的两篇文章一共对 17 个功能点的技术原理进行了分析: 编辑器 自定义组件 拖拽 删除组件.调整图层层级 放大缩小 撤消.重做 组件属性设置 吸附 预览.保存代码 绑 ...

  5. android四大组件之四-BroadCast实现原理分析

    前言: 一开始的目标是解决各种各样的ANR问题的,但是我们知道,ANR总体上分有四种类型,这四种ANR类型有三种是和四大组件相对应的,所以,如果想了解ANR发生的根因,对安卓四大组件的实现原理必须要懂 ...

  6. 可视化拖拽组件库一些技术要点原理分析(二)

    本文是对<可视化拖拽组件库一些技术要点原理分析>[1]的补充.上一篇文章主要讲解了以下几个功能点: 1.编辑器2.自定义组件3.拖拽4.删除组件.调整图层层级5.放大缩小6.撤消.重做7. ...

  7. Android Jetpack组件之Hilt使用

    前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. And ...

  8. Android Jetpack组件App Startup简析

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  9. Android Jetpack组件之WorkManger使用介绍

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

最新文章

  1. Google advertiser api开发概述——部分失败
  2. Python计算机视觉:第十章 OpenCV
  3. 修改GitHub记录中的invalid-email-address
  4. 高并发,分布式系统要点
  5. 动态绑定 datagridview
  6. MKCMS6.2.3视频程序源码修复列表页
  7. 小区重选优先级_NR小区重选理论研究
  8. 圆柱体积怎么算立方公式_祖暅原理和球的体积公式
  9. PHP 获取微视无水印源地址_如何提取获取下载美拍、微视、微拍等手机视频
  10. JPA并发save失效
  11. C# AHP层次分析法:一致性校验
  12. 充值校园卡显示服务器异常,调查| 为何这次校园卡系统故障时间这么长?
  13. 车联网TBOX国六OBD排放终端远程在线监控系统
  14. mac 截图工具只能截取桌面问题
  15. atitit 音频 项目 系列功能表 音乐 v3 t67.docx Atitit 音频 项目 系列功能表 1.音频 音乐 语言领域的功能表 听歌识曲功能 酷我功能。 铃声 功能。。 音频切割(按
  16. 最实用 DC终极扫盲大辞典! --- 让你对DC完全了解的88条名词解释(一)
  17. 2021-09-23记录下wifi调试流程
  18. 利用python爬取甲骨文图片及其对应的汉字含义,共1062个甲骨文,百度云下载
  19. 金一文化推出“B站2233 x中央芭蕾舞团”限量手办
  20. 查看oracle版本命令

热门文章

  1. MYSQL中的连接查询
  2. 基于 Sobol 序列和纵横交叉策略的麻雀搜索算法-附代码
  3. H3C(26)——GRE
  4. 向vue中集成electron(使用electron-builder插件直接将electron集成到自己的项目)
  5. 【原创】S7-1500与费斯托电缸CMMP-M3的profinet通讯
  6. bootstrap栅格系统布局原理
  7. 开源阅读书源_阅读3.20.0518追书神器 海量书源 免费开源无广告
  8. 查找数组中某个对象的位置
  9. pdf怎么压缩的小一点?pdf压缩大小如何压缩?
  10. java计算机毕业设计超市订单管理系统源码+数据库+系统+lw文档+mybatis+运行部署