Android开发的这一年里,Jetpack的Room源码是怎么狠狠奖励我的?

简述

Android Jetpack的出现统一了Android开发生态,各种三方库逐渐被官方组件所取代。Room也同样如此,逐渐取代竞品成为最主流的数据库ORM框架。这当然不仅仅因为其官方身份,更是因为其良好的开发体验,大大降低了SQLite的使用门槛。

Room是Google官方在SQLite基础上封装的一款数据持久库,是Jetpack全家桶的一员,和Jetpack其他库有着可以高度搭配协调的天然优势。Room使用APT技术,大大简化了使用SQLite的代码量,只需使用注解配合少量代码即可实现高效的数据库操作。

Room基本介绍

框架特点

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

  • 编译期的SQL语法检查
  • 开发高效,避免大量模板代码
  • API设计友好,容易理解
  • 可以与RxJava、 LiveData 、 Kotlin Coroutines等进行桥接

添加依赖

dependencies {
  implementation "androidx.room:room-runtime:2.2.5"
  kapt "androidx.room:room-compiler:2.2.5"
}

基本组件

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

  • Database: 访问底层数据库的入口
  • Entity: 代表数据库中的表(table),一般用注解
  • Data Access Object (DAO): 数据库访问者

这三个组件的概念也出现在其他ORM框架中,有过使用经验的同学理解起来并不困难: 通过Database获取DAO,然后通过DAO查询并获取entities,最终通过entities对数据库table中数据进行读写

实例实战

​
insert:使用注解 @Insert,Room 会自动将所有参数在单个事物中插入数据库
@Insert
public fun inertUser (user: User)  // 单个参数可以返回 long
​
@Insert
public fun insertUserList (array: Array<User>)  // 参数为集合可以返回 long []
数据库添加 User
val user = User()
user.name = "赵云 编号 = $number"
val address = Address()
address.street = "成都接头"
address.state = "蜀汉"
address.city = "常山"
address.postCode = 10010
user.address = address
userDao.inertUser (user)  // 添加 User
添加数据结果:
​
​
upadte:使用 @Update 注解
@Update
public fun update (user: User)   // 可以让此方法返回一个 int 值,表示数据库中更新的行数  
 
 
val user = User()
user.id = 1
user.name = "张翼德"
address.city = "涿郡"
.....
userDao.update(user)
点击 Update 后再查询结果:此时的赵云已经改为张翼徳了
​
​
delete:使用 @Delete 注解
@Delete 
public fun delete (user: User)   // 可以返回一个 int 值,表示从数据库中删除的行数
 
 
val user = User()
user.id = 1    // 要删除的主键 id
userDao.delete(user)
点击 delete 后再次查询数据:编号为 1 的数据已被删除
​
​
查询信息 :@Query 注解对数据库执行读 / 写操作
@Query("SELECT * FROM user")
public fun selectAll (): Array<User>   // 查询所有数据
 
@Query("SELECT * FROM user WHERE name = :name")
public fun selectUser (name:String): Array<User>   // 条件查询
返回列的子集:创建子类在每个属性中使用 @ColumnInfo (name = "name") 标记对应数据库中的列名
public class UserTuple {                  // 1、根据要查询的字段创建 POJO 对象      
  @ColumnInfo(name = "name")
  public var name: String? = null
  @ColumnInfo(name = "city")
  public var city: String? = null
}
 
@Query ("SELECT name ,city FROM user")  // 2、查询的结果会映射到创建的对象中
  public List<UserTuple> loadFullName();
 
 
val userList = userDao.loadFullName()
for (userTuple in userList) {
  stringBuilder.append(userTuple.name)
      .append("  ")
      .append(userTuple.city)
      .append("\n")
}
输出的结果:只有 name 和 city 两列
​
​
范围条件查询 :查询城市中所有用户
@Query("SELECT name ,street FROM user WHERE city IN (:cityArray)")
fun loadUserInCity(cityArray: Array<String>): List<UserTuple>
 
 
val userList = userDao.loadUserInCity (arrayOf ("常山"))  // 查询常山,只会出现赵云不会出现张翼德
​
​
Observable 查询:使用 LiveData 作为查询方法的返回值,注册观察者后,数据表更改时自动更新 UI
@Query("SELECT name ,street FROM user WHERE city IN (:cityArray"))
fun loadUserInCityLive(cityArray: Array<String>): LiveData<List<UserTuple>>
​
​
​
private lateinit var liveData: LiveData<Array<UserTuple>>  // 定义一个 LiveData
get() {
return userDao.loadUserInCityLive (arrayOf ("常山"))
}
​
val observer = Observer<Array<UserTuple>> {    // 定义一个观察者
  val stringBuilder = StringBuilder()
  for (index in it!!.indices) {
    val userTuple = it[index]
    stringBuilder.append(userTuple.name)
        .append("  ")
        .append(userTuple.name)
        .append("  \n")
  }
  tv_main_show.text = stringBuilder.toString()
}
liveData.observe (this, observer)   // 注册观察者
运行结果:此时当添加数据时,UI 会自动更新;
​
RxJava 查询 :返回 Observable 实例可以使用 RxJava 订阅观察者
@Query("SELECT * FROM user WHERE id = :id LIMIT 1")
fun loadUserRxJava(id:Int) : Flowable<User>
​
​
userDao.loadUserRxJava(4)
    .subscribe(Consumer {
      val stringBuilder = StringBuilder()
      stringBuilder.append(it.id)
          .append("  ")
          .append(it.name)
          .append("  \n")
      tv_main_show.text = stringBuilder.toString()
    })
​
​
 Cursor 查询:返回 Cursor 对象
fun loadUserCursor(id:Int) : Cursor
多表查询:根据表的外键多表查询
@Query("SELECT user.name AS userName, pet.name AS petName "
     + "FROM user, pet "
     + "WHERE user.id = pet.user_id")

源码分析

从上面的Demo代码可以看出,Room有很多的注解,实际上Room正是通过APT注解处理器,自动生成了许多代码,避免使用者在为了使用数据库编写重复的模板代码。

1.Room.databaseBuilder.build()

数据库使用的入口,也就是构造RoomDatabase的实例,它里面会根据buider中做的配置,进行一系列赋值操作,生成一个 DatabaseConfiguration对象,然后传入的 class 对象,调用Class.newInstance()方法,获取到RoomDatabase的实例。 接着就是调用RoomDatabase.init进行初始化操作。

public T build() {
    //...省略
    DatabaseConfiguration configuration =
                    new DatabaseConfiguration(
                            mContext,
                            mName,
                            factory,
                            mMigrationContainer,
                            mCallbacks,
                            mAllowMainThreadQueries,
                            mJournalMode.resolve(mContext),
                            mQueryExecutor,
                            mTransactionExecutor,
                            mMultiInstanceInvalidation,
                            mRequireMigration,
                            mAllowDestructiveMigrationOnDowngrade,
                            mMigrationsNotRequiredFrom,
                            mCopyFromAssetPath,
                            mCopyFromFile,
                            mCopyFromInputStream,
                            mPrepackagedDatabaseCallback,
                     mTypeConverters);
     T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
     db.init(configuration);
     return db;
}

2.RoomDatabase.init()

调用了createOpenHelper方法,createOpenHelper方法实现在AppDatabase_Impl中,创建了RoomOpenHelper,RoomOpenHelper继承SupportSQLiteOpenHelper.Callback。 引申:注意这里的 AutoCloser对象,这里没有仔细研究它的代码,但是它应该是代理持有了一个 SupportSQLiteOpenHelper对象,可以实现在提交数据库事务之后,自动的判断并close数据库。 从这里就可以看出,Room实际上是对 SQLite的再次封装,但是通过 APT 以及其他辅助类,使得Room的比直接用SQLite要简便很多。

@Override
  protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
    final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration,
                                                                              new RoomOpenHelper.Delegate(2) {
       //...省略
      }
}
​
@CallSuper
public void init(@NonNull DatabaseConfiguration configuration) {
    mOpenHelper = createOpenHelper(configuration);
​
    // Configure SqliteCopyOpenHelper if it is available:
    SQLiteCopyOpenHelper copyOpenHelper = unwrapOpenHelper(SQLiteCopyOpenHelper.class,
            mOpenHelper);
    if (copyOpenHelper != null) {
        copyOpenHelper.setDatabaseConfiguration(configuration);
    }
​
    AutoClosingRoomOpenHelper autoClosingRoomOpenHelper =
            unwrapOpenHelper(AutoClosingRoomOpenHelper.class, mOpenHelper);
​
    if (autoClosingRoomOpenHelper != null) {
        mAutoCloser = autoClosingRoomOpenHelper.getAutoCloser();
        mInvalidationTracker.setAutoCloser(mAutoCloser);
    }  
    
    // ... 省略
}            

3.AppDatabase.getUserDao()

AppDatabase是我们继承自RoomDatabase的抽象类,我们在里面添加了获取UserDao的方法,Room 会通过APT技术,在编译之后自动帮我们添加对应的实现。

@Override
  public UserDao getUerDao() {
    if (_userDao != null) {
      return _userDao;
    } else {
      synchronized(this) {
        if(_userDao == null) {
          _userDao = new UserDao_Impl(this);
        }
        return _userDao;
      }
    }
  }

4.UserDao_Impl

可以看出来,UserDao_Impl根据注解,具体的实现了我们在 UserDao接口中添加的方法,其中的关键点就在于,根据我们添加在注解中的 sql 语句,生成了对数据库的访问代码。

@Override
public void insertUserRecord(final User user) {
  __db.assertNotSuspendingTransaction();
  __db.beginTransaction();
  try {
    __insertionAdapterOfUser.insert(user);
    __db.setTransactionSuccessful();
  } finally {
    __db.endTransaction();
  }
}
​
@Override
public User geUerById(final String id) {
  final String _sql = "select *from User where id = (?)";
  final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
  int _argIndex = 1;
  if (id == null) {
    _statement.bindNull(_argIndex);
  } else {
    _statement.bindString(_argIndex, id);
  }
  __db.assertNotSuspendingTransaction();
  final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
  try {
    final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
    final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");
    final User _result;
    if(_cursor.moveToFirst()) {
      final String _tmpId;
      if (_cursor.isNull(_cursorIndexOfId)) {
        _tmpId = null;
      } else {
        _tmpId = _cursor.getString(_cursorIndexOfId);
      }
      final String _tmpName;
      if (_cursor.isNull(_cursorIndexOfName)) {
        _tmpName = null;
      } else {
        _tmpName = _cursor.getString(_cursorIndexOfName);
      }
      _result = new User(_tmpId,_tmpName);
    } else {
      _result = null;
    }
    return _result;
  } finally {
    _cursor.close();
    _statement.release();
  }
}

总结

本文只是对Room的一个简单分析,正如在一开始的概述里面说的那样:Room是对SQLite数据库的抽象,它提供了很多便利的API和注解等,简化了使用者使用数据库的方式。本文没有分析 Room和 LiveData结合使用的情况,因为笔者公司的项目还没能引入 LiveData。

抛开这一点不谈,个人认为它有两个比较显著的优点: 1、当然就是通过APT技术,TypeConverter 等,简化了使用,减少了大量的访问数据库的模板代码。 2、是文中也有提到的,它对数据库升级的优化,除了可以避免两个分支同时升级数据库但是合并不冲突导致的错误之外,它还提供了对数据库升级做单元测试的工具类,安全性提升很多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/817.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GIS(地理信息系统/地理信息科学)职称评审三:中科院和人社部职称评审结果公示对比

目录1.前言2.中科院3.人社部3.1 初级、中级3.2 高级、正高级3.3 公示时间4. 证书5. 程序员要不要评职称&#xff1f;6.总结1.前言 我们在前两篇已经讲过了GIS&#xff08;地理信息系统/地理信息科学&#xff09;怎么评职称&#xff1f;以及中科院和人社部职称评审所需材料内容对…

Qss样式表语法

QSS样式表语法 更多精彩内容&#x1f449;个人内容分类汇总 &#x1f448;&#x1f449;QSS样式学习 &#x1f448;文章目录QSS样式表语法[toc]概述一、样式规则二、选择器类型三、子控件四、伪状态五、样式表冲突解决六、级联七、继承八、命名空间中的控件概述 Qt样式表的概念…

2023年了,还是没学会内卷....

先做个自我介绍&#xff1a;我&#xff0c;普本&#xff0c;通信工程专业&#xff0c;现在飞猪干软件测试&#xff0c;工作时长两年半。 回望疫情纪元&#xff0c;正好是实习 毕业这三年。要说倒霉也是真倒霉&#xff0c;互联网浪潮第三波尾巴也没抓住&#xff0c;狗屁造富神…

软件缺陷详解

软件缺陷报告 知识点 软件缺陷的定义缺陷产生的原因如何编写缺陷报告缺陷报告的书写准则 简介 软件测试的目的是为了发现尽可能多的缺陷&#xff0c;这里的缺陷是一种泛称&#xff0c;他可以指功能的错误&#xff0c;也可以指性能低下&#xff0c;或者易用性差等。执行软件…

深度学习必备知识——模型数据集Yolo与Voc格式文件相互转化

在深度学习中&#xff0c;第一步要做的往往就是处理数据集,尤其是学习百度飞桨PaddlePaddle的小伙伴&#xff0c;数据集经常要用Voc格式的&#xff0c;比如性能突出的ppyolo等模型。所以学会数据集转化的本领是十分必要的。这篇博客就带你一起进行Yolo与Voc格式的相互转化&…

力扣-超过经理收入的员工

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;181. 超过经理收入的员工二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其…

Android之屏幕适配方案

在说明适配方案之前&#xff0c;我们需要对如下几个概念有所了解&#xff1a;屏幕尺寸&#xff0c;屏幕分辨率&#xff0c;屏幕像素密度。 屏幕尺寸 屏幕尺寸指屏幕的对角线的物理长度&#xff0c;单位是英寸&#xff0c;1英寸2.54厘米。 比如常见的屏幕尺寸&#xff1a;5.0、5…

组件库项目搭建

创建项目 使用pnpm create vite@latest 命令创建项目。 输入项目名,选择对应参数。 删除不需要的文件 添加pnpm-workspace.yaml 在项目根目录下创建一个pnpm-workspace.yaml文件,配置如下: packages:- demo # 存放组件示例代码- packages # packages 目录下都是组件包…

【pygame游戏】Python实现蔡徐坤大战篮球游戏【附源码】

前言 话说在前面&#xff0c;我不是小黑子~&#x1f60f; 本文章纯属技术交流~娱乐 前几天我获得了一个坤坤打篮球的游戏&#xff0c;也给大家分享一下吧~ 好吧&#xff0c;其实并不是这样的游戏&#xff0c;往下慢慢看吧。 准备工作 开发环境 Python版本&#xff1a;3.7.8 …

右值和右值引用(C++11新特性)

文章目录右值VS左值右值引用VS左值引用定义move函数左值引用&&右值引用 与 函数重载模板完美转发左值引用的意义移动构造&&移动赋值默认移动构造&&赋值右值VS左值 关于什么是右值什么是左值&#xff0c;我们是这样判断的&#xff1a; 右值&#xff1…

VSCode使用技巧,代码编写效率提升2倍以上!

VSCode是一款开源免费的跨平台文本编辑器&#xff0c;它的可扩展性和丰富的功能使得它成为了许多程序员的首选编辑器。在本文中&#xff0c;我将分享一些VSCode的使用技巧&#xff0c;帮助您更高效地使用它。 1. 插件 VSCode具有非常丰富的插件生态系统&#xff0c;通过安装插…

Python直接复制已有的venv虚拟环境以创建新的虚拟环境

Python venv创建的虚拟环境复制到其他路径&#xff0c;如何断开与原始虚拟环境的连接&#xff0c;成为一个全新的虚拟环境&#xff0c;且两个虚拟环境之间的更新互不影响&#xff1f;1.软件环境⚙️2.问题描述&#x1f50d;3.解决方法&#x1f421;3.1.方法1&#xff1a;先复制…

用Python Flask为女朋友做一个简单的网站(附可运行的源码)

&#x1f31f;所属专栏&#xff1a;献给榕榕&#x1f414;作者简介&#xff1a;rchjr——五带信管菜只因一枚&#x1f62e;前言&#xff1a;该专栏系为女友准备的&#xff0c;里面会不定时发一些讨好她的技术作品&#xff0c;感兴趣的小伙伴可以关注一下~&#x1f449;文章简介…

什么是PCB走线的3W原则

在设计PCB的时候我们会经常说到3W原则&#xff0c; 它指的是两个PCB走线它们的中心间距不小于3倍线宽&#xff0c;这个W就是PCB走线的宽度。这样做的目的主要是为了减小走线1和走线2之间的串扰&#xff0c;一般对于时钟信号&#xff0c;复位信号等一些关键信号需要遵循3W原则。…

Vue插槽理解

Vue插槽理解插槽插槽 slot又名插槽&#xff0c;vue内容分发机制&#xff0c;组件内部的模板引擎使用slot元素作为承载分发内容的出口 插槽slot是子组件的一个模板标签元素&#xff0c;而这一个元素是否显示&#xff0c;以及怎么显示是由父组件决定的 slot分为三类&#xff1a;默…

链表带环问题(详解)

&#x1f506;链表带环问题&#xff08;详解&#xff09;&#x1f506;I 给定一个链表&#xff0c;判断链表中是否有环。&#x1f506;II 给定一个链表&#xff0c;返回链表开始入环的第一个结点。 如果链表无环&#xff0c;则返回 NULL。&#x1f506;复制带随机指针的链表&am…

集成方法!

目录 关注降低variance,选择bias较小的基学习器 Bagging Stacking Random Forest 关注降低bias,选择variance较小的基学习器 Adaboost Boosting 关注降低variance,选择bias较小的基学习器 Bagging 给定m个样本的数据集&#xff0c;利用有放回的随机采样法&#xff0c;得…

【Linux】操作系统(Operator System)

操作系统&#xff08;Operator System &#xff09;一、操作系统的概念二、操作系统的作用三、系统调用和库函数一、操作系统的概念 操作系统是一组控制和管理计算机软硬件资源&#xff0c;为用户提供便捷使用的计算机程序的集合&#xff0c;是配置在计算机硬件系统上的第一层…

模拟实现字符串有关函数(详细讲解)

在编写程序时&#xff0c;我们都喜欢写出简便并且效率高的代码&#xff0c;那么此时库函数中的有些函数就是我们的不二之选&#xff0c;那么&#xff0c;大家汇米你实现吗&#xff1f;下面就先从我们最简单的字符串函数说起&#xff1a; 1.strlen 这个是函数的格式&#xff0c…

做了个springboot接口参数解密的工具,我给它命名为万能钥匙(已上传maven中央仓库,附详细使用说明)

前言&#xff1a;之前工作中做过两个功能&#xff0c;就是之前写的这两篇博客&#xff0c;最近几天有个想法&#xff0c;给它做成一个springboot的start启动器&#xff0c;直接引入依赖&#xff0c;写好配置就能用了 springboot使用自定义注解实现接口参数解密&#xff0c;普通…
最新文章