博客
关于我
Android Jetpack组件之 Room使用-源码
阅读量:167 次
发布时间:2019-02-28

本文共 18326 字,大约阅读时间需要 61 分钟。

1、前言

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

Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:

  • Android Jetpack组件总览
  • Android Jetpack 组件之 Lifecycle使用
  • Android Jetpack 组件之 Lifecycle源码
  • Android Jetpack组件之ViewModel使用
  • Android Jetpack组件之 LiveData使用-源码
  • Android Jetpack组件之 Paging使用-源码
  • Android Jetpack组件之 Room使用-源码
  • Android Jetpack组件之Navigation使用-源码
  • Android Jetpack组件之WorkManger使用介绍
  • Android Jetpack组件App Startup简析
  • Android Jetpack组件之Hilt使用

本系列文章是各处copy过来的,个人感觉所有的开发者都应该尽早的熟悉Jetpack组件,相信一定会被它的魅力所吸引,最近也在完成一个使用以上所有组件实现的项目,作为对Jetpack组件的项目实践,下面来分析一下每个组件对项目开发的帮助。

2、Room 简介

Room是Google提供的一个ORM库。Room提供了三个主要的组件:

  • @Database:@Database用来注解类,并且注解的类必须是继承自RoomDatabase的抽象类。该类主要作用是创建数据库和创建Daos(data access objects,数据访问对象)。
  • @Entity:@Entity用来注解实体类,@Database通过entities属性引用被@Entity注解的类,并利用该类的所有字段作为表的列名来创建表。
  • @Dao:@Dao用来注解一个接口或者抽象方法,该类的作用是提供访问数据库的方法。在使用@Database注解的类中必须定一个不带参数的方法,这个方法返回使用@Dao注解的类

3、Room数据库使用

数据库的创建

  • 包含数据库持有者,并作为应用程序持久关系数据的基础连接的主要访问点,使用@Database注解,注解类应满足以下条件:
  1. 数据库必须是一个抽象类 RoomDatabase的扩展类
  2. 在注释中包括与数据库关联的实体列表
  3. 必须包含一个具有0个参数且返回带@Dao注释的类的抽象方法
  4. 通过调用 Room.databaseBuilder()或 获取实例Room.inMemoryDatabaseBuilder()创建数据库实例
  5. 使用单例实例化数据库对象
@Database(entities = {User.class}, version = 1)  // 注释public abstract class AppDatabase extends RoomDatabase {    public abstract UserDao userDao();  // 抽象方法}
  • 以单例形式对外提供RoomDataBase实例
public static UserDataBase getInstance(Context context) {    if (userDataBase == null) {        synchronized (UserDataBase.class) {            if (userDataBase == null) {                userDataBase = Room.databaseBuilder(context.getApplicationContext()                        , UserDataBase.class, "user_data").build();            }        }    }    return userDataBase;}

定义实体数据:表示数据库中的表

  • @Entity
  1. 使用@Entity注解实体类,Room会为实体中定义的每个字段创建一列,如果想避免使用@Ignore注解
  2. Room默认使用类名作为数据库表名,要修改表名使用 @Entity 的 tableName属性
  • 主键
  1.  @PrimaryKey :至少定义一个字段作为主键
  2. 如果自增长ID 使用设置@PrimaryKey的 autoGenerate 属性
  3. 使用组合主键 使用@Entity 的@primaryKeys属性
  4. Room 默认使用字段名成作为列名,要修改使用 @ColumnInfo(name = "***") 
@Entity(tableName = "userDataBase")class User {    @PrimaryKey(autoGenerate = true)    // 单个主键设置为自增长    public var id = 0    @ColumnInfo(name = "nameUser")  // 定义列名    public var name: String? = null} @Entity(primaryKeys = ["id", "name"])  // 组合组件

添加索引@Entity

  • 使用 @Entity 的indices 属性,列出要包含在索引或复合索引中的列的名称
@Entity(indices = [Index("nameUser"), Index(value = ["name"])])  // 创建索引@Entity(indices = [Index("nameUser"), Index(value = ["name"] ,unique = true)]) //唯一索引

外键约束@ForeignKey

  • 使用@ForeignKey 注释定义其与实体的关系;ForeignKey中 entity 为要关联的父实体类;parentColumns 为关联父实体类的列名;childColumns此实体类中的列名
@Entity(foreignKeys = [ForeignKey(entity = User::class,        parentColumns = ["id"],        childColumns = ["user_id"])])class Book {    @PrimaryKey    var bookId: Int = 0    var title: String? = null    @ColumnInfo(name = "user_id")    var userId: Int = 0}

嵌套对象@Embedded 

  • 使用 @Embedded 注释来表示要分解到表中子字段的对象(此时数据库的列为两个类中所有的字段)
class Address {    public var street: String? = null    public var state: String? = null    public var city: String? = null    @ColumnInfo(name = "post_code")    public var postCode = 0} // 在User实体中引入Address@Embeddedpublic var address: Address? = null

访问数据库

  • 使用@DAO注解:包含用于访问数据库的方法
@Dao                   public interface UserDao {    @Insert      // 添加数据注解    void insertAll(User... users);     @Delete    // 删除数据注解    void delete(User user);}

4、实例实战

  • insert:使用注解@Insert,Room会自动将所有参数在单个事物中插入数据库
@Insertpublic fun inertUser(user: User)   // 单个参数可以返回 long @Insertpublic fun insertUserList(array: Array
) // 参数为集合可以返回long[]
  • 数据库添加User
val user = User()user.name = "赵云 编号 = $number"val address = Address()address.street = "成都接头"address.state = "蜀汉"address.city = "常山"address.postCode = 10010user.address = addressuserDao.inertUser(user)   // 添加User

添加数据结果:

  • upadte:使用 @Update注解
@Updatepublic fun update(user: User)     // 可以让此方法返回一个int值,表示数据库中更新的行数    val user = User()user.id = 1user.name = "张翼德"address.city = "涿郡".....userDao.update(user)

点击 Update 后再查询结果:此时的赵云已经改为张翼徳了:

  • delete:使用@Delete注解
@Delete public fun delete(user: User)    //可以返回一个int值,表示从数据库中删除的行数  val user = User()user.id = 1      // 要删除的主键 iduserDao.delete(user)

点击delete后再次查询数据:编号为1的数据已被删除。

  • 查询信息 :@Query注解对数据库执行读/写操作
@Query("SELECT * FROM user")public fun selectAll(): Array
// 查询所有数据 @Query("SELECT * FROM user WHERE name = :name")public fun selectUser(name:String): Array
// 条件查询
  • 返回列的子集:创建子类在每个属性中使用@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
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
): List
val userList = userDao.loadUserInCity(arrayOf("常山")) // 查询常山,只会出现赵云不会出现张翼德

  • Observable查询:使用LiveData作为查询方法的返回值,注册观察者后,数据表更改时自动更新UI
@Query("SELECT name ,street FROM user WHERE city IN (:cityArray"))fun loadUserInCityLive(cityArray: Array
): LiveData
> private lateinit var liveData: LiveData
> // 定义一个LiveDataget() {return userDao.loadUserInCityLive(arrayOf("常山"))} val observer = Observer
> { // 定义一个观察者 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
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")

5、更新数据库

  • 编写 Migration 的实例。每个 Migration 类指定一个startVersion和endVersion
  • Room运行每个 Migration 类的 migrate() 方法,使用正确的顺序将数据库迁移到更高版本
static final Migration MIGRATION_1_2 = new Migration(1, 2) { //由1升级到版本2    @Override    public void migrate(SupportSQLiteDatabase database) {        database.execSQL("CREATE TABLE book (id  INTEGER , name TEXT )")    }}; static final Migration MIGRATION_2_3 = new Migration(2, 3) { //由2升级到版本3    @Override    public void migrate(SupportSQLiteDatabase database) {         database.execSQL("ALTER TABLE user ADD COLUMN strength INTEGER NOT NUll DEFAULT 0")  //添加strength列    }};  Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")        .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();

升级完数据库后再次查询,结果显示数据库增加了strength列名:

6、引用复杂数据

Room提供了在原始类型和目标类型之间进行转换的功能,但不允许实体之间的对象引用,对于其他类型之间的使用需要自定义转换器

使用类型转换器

使用TypeConverter,它将自定义类转换为Room可以保留的已知类型,如:想保存Date类型,而Room无法持久化实例Date却可以实例long,因此提供和long的相互转换

public class Converters {     @TypeConverter    public static Date fromTimestamp(Long value) {        return value == null ? null : new Date(value);    }     @TypeConverter    public static Long dateToTimestamp(Date date) {        return date == null ? null : date.getTime();    }}
  • 在抽象数据库类中添加转换注解
@TypeConverters({Converters.class})
  • 使用 类型转换器
@Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")List findUsersBornBetweenDates(Date from, Date to);

以上就是数据库Room的使用简介了,基本数据库的增删改查以及常见的设置都在其中了,下面我们来看看Room是如何实现这些过程的,从源码角度分析数据库。

7、源码分析

数据库的创建和升级

Room数据库实例的创建由Room.databaseBuilder(context.applicationContext,RoomTestData::class.java, "Sample.db").build()开始的,从代码中看出时使用Builder模式创建DataBase,所以我们先看看RoomDatabase.Builde类

  • RoomDatabase.Builder:除了包含Room的实现类、数据库名称的常规设置外,也包含了数据库的升级信息
@NonNullpublic Builder
addMigrations(@NonNull Migration... migrations) { // 添加数据库版本升级信息 if (mMigrationStartAndEndVersions == null) { mMigrationStartAndEndVersions = new HashSet<>(); } for (Migration migration: migrations) { mMigrationStartAndEndVersions.add(migration.startVersion); mMigrationStartAndEndVersions.add(migration.endVersion); } mMigrationContainer.addMigrations(migrations); return this;}
  • build():创建并初始化数据库
private static final String DB_IMPL_SUFFIX = "_Impl"。。。。。。T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX); // 创建DataBase实现类的实例 db.init(configuration);  // 初始化数据库
  • getGeneratedImplementation():反射创建DataBase的实现类
static 
T getGeneratedImplementation(Class
klass, String suffix) { final String fullPackage = klass.getPackage().getName(); String name = klass.getCanonicalName(); final String postPackageName = fullPackage.isEmpty() ? name : (name.substring(fullPackage.length() + 1)); // 获取类名 final String implName = postPackageName.replace('.', '_') + suffix; // 拼接类名 //noinspection TryWithIdenticalCatches try { @SuppressWarnings("unchecked") final Class
aClass = (Class
) Class.forName( fullPackage.isEmpty() ? implName : fullPackage + "." + implName); // 获取自动生成的类文件 return aClass.newInstance(); // 创建并返回实例 } catch (ClassNotFoundException e) { 。。。。。。 } }

此处获取到的是系统根据注解自动创建的是实现类RoomDataBase_Impl,Room采用的是,根据@DataBase和@Dao的注解,自动生成这两个注解标记的实现类,系统创建类如下图:

  • RoomTestData_Impl:系统自动生成的实现类
public class RoomTestData_Impl extends RoomTestData {  private volatile UserDao _userDao;......  @Override  public UserDao userDao() {       if (_userDao != null) {      return _userDao;    } else {      synchronized(this) {        if(_userDao == null) {          _userDao = new UserDao_Impl(this);  // 创建并返回UserDao的实例        }        return _userDao;      }    }  }}

从上面的代码中看出,系统自动创建了RoomTestData的实现类,并重写了抽象方法userDao(),在userDao()中使用单例的方式提供UserDao的实现类UserDao_Impl,UserDao_Impl的形成和RoomTestData_Impl的生成一样,在代码中从DataBase中调用userDao返回的就是UserDao_Impl的实例;

接着分析数据库的创建,在上面的代码中有一句数据库的初始化代码db.init(),在db.init()的方法中会调用RoomDataBase中的抽象方法createOpenHelper(),这里调用的是createOpenHelper()就是RoomTestData_Impl自动实现的方法:

protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {   final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(3) {    @Override    public void createAllTables(SupportSQLiteDatabase _db) {      _db.execSQL("CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `strength` INTEGER NOT NULL, `name` TEXT, `street` TEXT, `state` TEXT, `city` TEXT, `post_code` INTEGER)");   // 创建数据库      _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");      _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"8ece9a1581b767a0f460940849e9b463\")");    }     @Override    public void dropAllTables(SupportSQLiteDatabase _db) {      _db.execSQL("DROP TABLE IF EXISTS `user`");  // 删除数据库    }    @Overrideprotected void validateMigration(SupportSQLiteDatabase _db) {  // 处理数据库的版本升级 。。。。。。}  }, "8ece9a1581b767a0f460940849e9b463", "061261cef54147a569851cbbb906c3be");} 。。。。。。  return _helper;}

上面的代码中执行一下操作:

  • 创建SupportSQLiteOpenHelper.Callback 的实例并重写方法
  • 在onCreate()中Sql语句创建user表和room_master_table表
  • 在dropAllTables()中创建删除数据库的SQL语句
  • 在validateMigration()中完成数据库的升级

上面SupportSQLiteOpenHelper.Callback 的实现类为RoomOpenHelper,下面一起看看RoomOpenHelper源码:

@Overridepublic void onCreate(SupportSQLiteDatabase db) {    updateIdentity(db);    mDelegate.createAllTables(db);  // mDelegate为上面创建的RoomOpenHelper.Delegate实例    mDelegate.onCreate(db);} @Overridepublic void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {if (mConfiguration != null) {    List
migrations = mConfiguration.migrationContainer.findMigrationPath( oldVersion, newVersion); if (migrations != null) { for (Migration migration : migrations) { migration.migrate(db); } mDelegate.validateMigration(db); // 调用validateMigration方法处理数据库的更新 updateIdentity(db); migrated = true; }}} @Overridepublic void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { onUpgrade(db, oldVersion, newVersion);}

从上面代码中可以看出,在onCreate()方法中调用了mDelegate.createAllTables(db),这里的mDelegate就是上面创建RoomOpenHelper方法中第二个参数RoomOpenHelper.Delegate,所以这里就是在onCreate()中创建了数据库,在onUPgrade()中调用 mDelegate.validateMigration(db)完成数据库的升级,到这里数据库的创建和升级已经介绍完毕了,下面就一起看看Room是如何访问数据库的。

数据库的访问

  • @Dao数据库的实现类: UserDao_Impl
private final RoomDatabase __db;   // 传入的数据库 private final EntityInsertionAdapter __insertionAdapterOfUser;  // 处理insert方法 private final EntityDeletionOrUpdateAdapter __deletionAdapterOfUser;   // 处理delete方法 private final EntityDeletionOrUpdateAdapter __updateAdapterOfUser;  // 处理update方法

在UserDao_Impl的类中除了数据库RoomDataBase实例外,还有三个成员变量分别为:__insertionAdapterOfUser、__deletionAdapterOfUser、__updateAdapterOfUser,从名字上可以看出来他们三个分别对应数据库增、删、改的三个操作,我们以insert操作为例,查看insert方法:

@Overridepublic void inertUser(User user) {  __db.beginTransaction();  try {    __insertionAdapterOfUser.insert(user);    __db.setTransactionSuccessful();  } finally {    __db.endTransaction();  }}

insert()方法的实现是在__insertionAdapterOfUser中执行的,查看__insertionAdapterOfUser的实现:

this.__insertionAdapterOfUser = new EntityInsertionAdapter
(__db) { @Override public String createQuery() { // 创建SupportSQLiteStatement时传入的Sql语句 return "INSERT OR ABORT INTO `user`(`id`,`strength`,`name`,`street`,`state`,`city`,`post_code`) VALUES (nullif(?, 0),?,?,?,?,?,?)"; } @Override public void bind(SupportSQLiteStatement stmt, User value) { stmt.bindLong(1, value.getId()); stmt.bindLong(2, value.getStrength()); if (value.getName() == null) { // 判断此列是否为null,部位Null则设置数据 stmt.bindNull(3); } else { stmt.bindString(3, value.getName()); } final Address _tmpAddress = value.getAddress(); if(_tmpAddress != null) { if (_tmpAddress.getStreet() == null) { stmt.bindNull(4); } else { stmt.bindString(4, _tmpAddress.getStreet()); } if (_tmpAddress.getState() == null) { stmt.bindNull(5); } else { stmt.bindString(5, _tmpAddress.getState()); } if (_tmpAddress.getCity() == null) { stmt.bindNull(6); } else { stmt.bindString(6, _tmpAddress.getCity()); } stmt.bindLong(7, _tmpAddress.getPostCode()); } else { stmt.bindNull(4); stmt.bindNull(5); stmt.bindNull(6); stmt.bindNull(7); } }};

__insertionAdapterOfUser的实例重写了两个方法:

  • createQuery():创建数据库插入数据的sql语句
  • bind():绑定数据库中每个列对应的值

__insertionAdapterOfUser.insert()

insert()方法中创建SupportSQLiteStatement的实例,并调用bind()完成数据的绑定,然后执行stmt.executeInsert()插入数据

public final void insert(T entity) {    final SupportSQLiteStatement stmt = acquire();  // 最终创建的是FrameworkSQLiteStatement的包装的SQLiteStatement实例    try {        bind(stmt, entity);  // 绑定要插入的数据        stmt.executeInsert();   // 提交保存数据,执行    } finally {        release(stmt);    }}  @Overridepublic long executeInsert() {  // 最终执行数据库的插入操作    return mDelegate.executeInsert();}
  • 查寻数据库

在UserDao_Impl中自动实现了查询的方法selectUser:

@Overridepublic User[] selectUser(String name) {  final String _sql = "SELECT * FROM user WHERE name = ?";  final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);   //   创建RoomSQLiteQuery   int _argIndex = 1;  if (name == null) {    _statement.bindNull(_argIndex);  } else {    _statement.bindString(_argIndex, name);  }  final Cursor _cursor = __db.query(_statement); //   执行查询反会Cursor  try {    final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");    final int _cursorIndexOfStrength = _cursor.getColumnIndexOrThrow("strength");    final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");    final int _cursorIndexOfStreet = _cursor.getColumnIndexOrThrow("street");    final int _cursorIndexOfState = _cursor.getColumnIndexOrThrow("state");    final int _cursorIndexOfCity = _cursor.getColumnIndexOrThrow("city");    final int _cursorIndexOfPostCode = _cursor.getColumnIndexOrThrow("post_code");    final User[] _result = new User[_cursor.getCount()];    int _index = 0;    while(_cursor.moveToNext()) {      final User _item;      final Address _tmpAddress;      if (! (_cursor.isNull(_cursorIndexOfStreet) && _cursor.isNull(_cursorIndexOfState) && _cursor.isNull(_cursorIndexOfCity) && _cursor.isNull(_cursorIndexOfPostCode))) {        _tmpAddress = new Address();        final String _tmpStreet;        _tmpStreet = _cursor.getString(_cursorIndexOfStreet);        _tmpAddress.setStreet(_tmpStreet);        final String _tmpState;        _tmpState = _cursor.getString(_cursorIndexOfState);        _tmpAddress.setState(_tmpState);        final String _tmpCity;        _tmpCity = _cursor.getString(_cursorIndexOfCity);        _tmpAddress.setCity(_tmpCity);        final int _tmpPostCode;        _tmpPostCode = _cursor.getInt(_cursorIndexOfPostCode);        _tmpAddress.setPostCode(_tmpPostCode);      }  else  {        _tmpAddress = null;      }      _item = new User();      final int _tmpId;      _tmpId = _cursor.getInt(_cursorIndexOfId);      _item.setId(_tmpId);      final int _tmpStrength;      _tmpStrength = _cursor.getInt(_cursorIndexOfStrength);      _item.setStrength(_tmpStrength);      final String _tmpName;      _tmpName = _cursor.getString(_cursorIndexOfName);      _item.setName(_tmpName);      _item.setAddress(_tmpAddress);      _result[_index] = _item;      _index ++;    }    return _result;  } finally {    _cursor.close();    _statement.release();  }}

上面执行的也是数据库的正常操作,先创建了RoomSQLiteQuery的实例,在调用db。query()执行查询,查询返回Cursor实例,最终从Cursor中获取信息转换为对象并返回数据。


到此Room的使用和源码执行流程就到此结束了,本文旨在执行的流程分析,具体的如何使用SQLite数据库操作的读者可以自己点击源码查看,不过使用的SQLite的查询和添加方法和平时使用的不同,读者想分析的话就会找到了,好了,希望本篇文章对想了解和使用Room组件的同学有所帮助!

你可能感兴趣的文章
Mysql之IN 和 Exists 用法
查看>>
MYSQL之REPLACE INTO和INSERT … ON DUPLICATE KEY UPDATE用法
查看>>
MySQL之SQL语句优化步骤
查看>>
MYSQL之union和order by分析([Err] 1221 - Incorrect usage of UNION and ORDER BY)
查看>>
Mysql之主从复制
查看>>