一、前言
最近简单看了下google推出的框架Jetpack,感受此框架的内容能够对平时的开发有很大的帮助,也能够解决不少开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。java
Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:sql
带你领略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
实战:从0搭建Jetpack版的WanAndroid客户端
上述时Android Architecture所提供的架构组件,本文主要从使用和源码的角度分析Room组件。数据库
二、Room 简介
Room是Google提供的一个ORM库。Room提供了三个主要的组件:架构
@Database:@Database用来注解类,而且注解的类必须是继承自RoomDatabase的抽象类。该类主要做用是建立数据库和建立Daos(data access objects,数据访问对象)。
@Entity:@Entity用来注解实体类,@Database经过entities属性引用被@Entity注解的类,并利用该类的全部字段做为表的列名来建立表。
@Dao:@Dao用来注解一个接口或者抽象方法,该类的做用是提供访问数据库的方法。在使用@Database注解的类中必须定一个不带参数的方法,这个方法返回使用@Dao注解的类
三、Room数据库使用
数据库的建立app
包含数据库持有者,并做为应用程序持久关系数据的基础链接的主要访问点,使用@Database注解,注解类应知足如下条件:
数据库必须是一个抽象类 RoomDatabase的扩展类
在注释中包括与数据库关联的实体列表
必须包含一个具备0个参数且返回带@Dao注释的类的抽象方法
经过调用 Room.databaseBuilder()或 获取实例Room.inMemoryDatabaseBuilder()建立数据库实例
使用单例实例化数据库对象
@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
使用@Entity注解实体类,Room会为实体中定义的每一个字段建立一列,若是想避免使用@Ignore注解
Room默认使用类名做为数据库表名,要修改表名使用 @Entity 的 tableName属性
主键
@PrimaryKey :至少定义一个字段做为主键
若是自增加ID 使用设置@PrimaryKey的 autoGenerate 属性
使用组合主键 使用@Entity 的@primaryKeys属性
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
@Embedded
public var address: Address? = null
访问数据库ide
使用@DAO注解:包含用于访问数据库的方法
@Dao
public interface UserDao {
@Insert // 添加数据注解
void insertAll(User... users);
@Delete // 删除数据注解
void delete(User user);
}
四、实例实战
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 后再查询结果:此时的赵云已经改成张翼徳了post
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{ // 一、根据要查询的字段建立POJO对象
@ColumnInfo(name = "name")
public var name: String? = null
@ColumnInfo(name = "city")
public var city: String? = null
}
@Query("SELECT name ,city FROM user") // 二、查询的结果会映射到建立的对象中
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")
五、更新数据库
编写 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列名:
六、引用复杂数据
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是如何实现这些过程的,从源码角度分析数据库。
七、源码分析
数据库的建立和升级
Room数据库实例的建立由Room.databaseBuilder(context.applicationContext,RoomTestData::class.java, "Sample.db").build()开始的,从代码中看出时使用Builder模式建立DataBase,因此咱们先看看RoomDatabase.Builde类
RoomDatabase.Builder:除了包含Room的实现类、数据库名称的常规设置外,也包含了数据库的升级信息
@NonNull
public Builder<T> 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, C> T getGeneratedImplementation(Class<C> 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<T> aClass = (Class<T>) 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`"); // 删除数据库
}
@Override
protected void validateMigration(SupportSQLiteDatabase _db) { // 处理数据库的版本升级
。。。。。。
}
}, "8ece9a1581b767a0f460940849e9b463", "061261cef54147a569851cbbb906c3be");
}
。。。。。。
return _helper;
}
上面的代码中执行一下操做:
建立SupportSQLiteOpenHelper.Callback 的实例并重写方法
在onCreate()中Sql语句建立user表和room_master_table表
在dropAllTables()中建立删除数据库的SQL语句
在validateMigration()中完成数据库的升级
上面SupportSQLiteOpenHelper.Callback 的实现类为RoomOpenHelper,下面一块儿看看RoomOpenHelper源码:
@Override
public void onCreate(SupportSQLiteDatabase db) {
updateIdentity(db);
mDelegate.createAllTables(db); // mDelegate为上面建立的RoomOpenHelper.Delegate实例
mDelegate.onCreate(db);
}
@Override
public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
if (mConfiguration != null) {
List<Migration> migrations = mConfiguration.migrationContainer.findMigrationPath(
oldVersion, newVersion);
if (migrations != null) {
for (Migration migration : migrations) {
migration.migrate(db);
}
mDelegate.validateMigration(db); // 调用validateMigration方法处理数据库的更新
updateIdentity(db);
migrated = true;
}
}
}
@Override
public 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方法:
@Override
public void inertUser(User user) {
__db.beginTransaction();
try {
__insertionAdapterOfUser.insert(user);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
insert()方法的实现是在__insertionAdapterOfUser中执行的,查看__insertionAdapterOfUser的实现
this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__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);
}
}
@Override
public long executeInsert() { // 最终执行数据库的插入操做
return mDelegate.executeInsert();
}
查寻数据库
在UserDao_Impl中自动实现了查询的方法selectUser:
@Override
public 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组件的同窗有所帮助!