ROOM简介
背景
为什么需要数据库的封装
- 容易在主线程使用数据库api
- SQL语法的正确性无法保证,必须等到运行时才能发现
- 表中的数据转化对象步骤繁琐,这也是ORM的作用
- 升级数据库时候字段变化容易出错
ROOM简介
Room是Android Jetpack包的一部分,为应用提供数据库支持。Room持久性库提供了一个基于SQLite的抽象层,以便在利用SQLite的全部功能的同时,实现更强大的数据库访问能力。
首先看下整体框架:
ROOM主要分为三部分组件: 1、负责操作数据库提供接口的DAO类 2、数据库表的类Entities 3、负责管理数据库以及实现接口访问的RoomDataBase
我们看下简单使用:
User:
@Entity
public class User {
@PrimaryKey
public int uid;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
}
UserDao:
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
List<User> getAll();
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
List<User> loadAllByIds(int[] userIds);
@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1")
User findByName(String first, String last);
@Insert
void insertAll(User... users);
@Delete
void delete(User user);
}
AppDatabase:
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
接下来我们只要调用创建数据库就能正常存取数据了:
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
使用起来还是很简单的,后续我们具体分析下试下原理
框架简介
在正式介绍流程之前,我们简单介绍下ROOM的源码大概框架:
我们简单分四个层面,最下面的是Android提供的基础接口,Androidx Sqlite是Androidx抽象出来的接口,接口基本都是映射系统提供的接口,Androidx Sqlite-FrameWork是针对其抽象的实现,这样做的好处是彻底与系统库解偶,一旦你出现更换系统接口封装接口可不受影响,虽然这概率不大。
刚刚我们主要梳理的是ROOM对系统接口的封装,那么ROOM业务层的框架又是什么样子的呢?
我们来看下整体的引用关系:
有点复杂,我们先梳理下流程
创建数据库
首先上图,创建数据库流程:
其实创建数据库主要就是一个初始化SupportSQLiteOpenHelper的过程,后续所有的数据库操作都需要依赖它
看代码,创建的入口是:
_INSTANCE = Room.databaseBuilder(context, TestDataBase.class, TABLE_NAME).build();
创建的逻辑主要就是RoomDatabase的创建,builder模式中的build主要执行以下两个方法:
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
db.init(configuration);
getGeneratedImplementation中,就是反射调用实例化Database_impl的过程,init中的主要作用就是构建SupportSQLiteOpenHelper。
mOpenHelper = createOpenHelper(configuration);
SupportSQLiteOpenHelper是Room与SQLite的唯一交互接口,ROOM将SQL连接的生命周期事件和真正的数据库操作分为了两部分,前者是SupportSQLiteOpenHelper.Callback(实现类是RoomOpenHelper),后者是FrameworkSQLiteOpenHelper本身逻辑,RoomOpenHelper有两个比较重要的方法,onCreate和onUpgrade,一个是数据库创建,一个是数据库的升级,还有些是数据库其他的生命周期事件通知。
数据库查询
再次之前先了解下RoomDatabase这个类,是ROOM三大组件中的操作数据库入口,我们之前提到SupportSQLiteOpenHelper是Room与SQLite的唯一交互接口,不难猜出RoomDatabase肯定持有了SupportSQLiteOpenHelper,不仅如此,还持有了SupportSQLiteDatabase,所以RoomDatabase不仅仅是替代了系统SQLiteDatabase,还扩展了SQLiteOpenHelper,这样搞业务层的分工更明确,这种组合模式让业务层更加灵活。
接下来咱们看下查询的流程,查询入口:
public User getUser(final int userId) {
//构建查询的SQL语句
final String _sql = "SELECT * FROM user where uid LIKE ?";
//获取对用的查询statement对象
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
...
//执行查询
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
...
}
说到了RoomSQLiteQuery,这里简单介绍下,RoomSQLiteQuery和SimpleSQLiteQuery是两个实现了数据绑定的实现类,这两者的区别在于RoomSQLiteQuery有缓存特定查询的RoomSQLiteQuery对象缓存,而SimpleSQLiteQuery没有。另外一个特点是RoomSQLiteQuery在绑定数据中并不需要拆装箱,所有的查询参数类型是事先预设,而SimpleSQLiteQuery是根据传入的object[]数据进行拆装箱进行绑定。
RoomSQLiteQuery的封装考虑了性能以及查询复用。
继续查询流程,流程图如下:
从RoomDataBase的查询接口,最终走到SQLiteDatabase.rawQueryWithFactory
public Cursor query(final SupportSQLiteQuery supportQuery,
CancellationSignal cancellationSignal) {
return mDelegate.rawQueryWithFactory(new SQLiteDatabase.CursorFactory() {
@Override
public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
String editTable, SQLiteQuery query) {
supportQuery.bindTo(new FrameworkSQLiteProgram(query));
return new SQLiteCursor(masterQuery, editTable, query);
}
}, supportQuery.getSql(), EMPTY_STRING_ARRAY, null, cancellationSignal);
}
数据库查询的步骤基本有两步,一个是SQL语句,还有一个是参数绑定,但是在执行查询的时候不是直接传入参数列表,而是在查询前一步通过RoomSQLiteQuery来绑定:
android.database.sqlite.SQLiteDirectCursorDriver#query
数据库增删改
ROOM的增删改,基本沿用了系统的接口,但是鉴于增删改的操作重复性,中间做了一层缓存管理,针对增删改设计了EntityInsertionAdapter、EntityDeletionOrUpdateAdapter两个类,前者用来实现增加,或者用来实现删除或者修改。
以insert为例:
public final void insert(T entity) {
final SupportSQLiteStatement stmt = acquire();
try {
bind(stmt, entity);
stmt.executeInsert();
} finally {
release(stmt);
}
}
与系统接口流程一致,先获取操作数据库的Statement,绑定参数,最后执行插入。
SharedSQLiteStatement的缓存是如何实现的呢,首先我们看下
private SupportSQLiteStatement getStmt(boolean canUseCached) {
final SupportSQLiteStatement stmt;
if (canUseCached) {
if (mStmt == null) {
mStmt = createNewStatement();
}
stmt = mStmt;
} else {
// it is in use, create a one off statement
stmt = createNewStatement();
}
return stmt;
}
获取Stmt时候可选择不使用缓存,如果首次的话会直接创建新的
private SupportSQLiteStatement createNewStatement() {
String query = createQuery();
return mDatabase.compileStatement(query);
}
这里创建的SQL语句针对不同的增删改有一定的复用性,比如insert:
public String createQuery() {
return "INSERT OR ABORT INTO `user` (`uid`,`name`,`age`,`gender`,`score`,`height`,`url`) VALUES (?,?,?,?,?,?,?)";
}
insert的流程图如下:
删改的流程与插入类似,不在此赘述。
除了以上的内容其实还有更新以及数据流的部分还没有提及,这个后续继续更新