SQLite Room
Room
由于SQLiteOpenHelper的接口调用起来比较繁琐。比如插入需要我们自己组装成一个contentValues,查询的时候需要自己将Cursor中的数据取出,这里有很多重复的逻辑,而room就是为了帮助我们解决这些让数据库操作更加便捷,Room是Google官方提供的数据库ORM框架。
对一个表的操作只要写如下代码
@Database(entities = {DebugEntity.class},version = 1)
public abstract class DebugDatabase extends RoomDatabase {
public abstract DebugDao debugDao();
}
@Dao
public interface DebugDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(DebugEntity... debugEntities);
@Update
void update(DebugEntity debugEntity);
@Delete
void delete(DebugEntity entity);
@Query("SELECT * from tb_debug WHERE id= :id")
Single<DebugEntity> loadWithId(int id);
}
@Entity(tableName = "tb_debug")
public class DebugEntity {
@PrimaryKey
private long id;
@ColumnInfo
private String content;
public DebugEntity() {
}
public DebugEntity(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
debugDatabase = Room.databaseBuilder(context, DebugDatabase.class, "debug_database")
.build();
//完成对数据库的操作
debugDatabase.debugDao().xxx()
-
有以上DataBase,Dao,Entity三个类,room的compiler(之前写的预编译文章)模块会在编译前帮我们生成类DataBase,Dao_Impl.
-
@Entity :将一个类标记为entity,这个类用于到数据库表的映射,每个entity至少有一个@PrimaryKey标记的字段,用@Ignore标记不需要持久化的字段,如果一个字段用transient修饰,自动忽略除非用ColunmInfo,@Embedde,@Relation注解.
-
@Dao: 将一个类标记为数据访问对象,用于定义如何和数据库交互,可以包括各种查询方法,用@Dao注解的类需要是一个接口或者是一个抽象类,如果和@DataBase关联在编译时Room将会生成实现类。建议有多个Dao类用于与多个数据表的交互。
-
@Database: 将一个类标记为RoomDatabase,这个类需要继承RoomDatabase并是一个抽象类,你可以用
Room.databaseBuilder,Room.inMemoryDatabaseBuilder得到实现类。在编译时Room会检测你在Dao中写的查询语句,如果有问题会及时的通知你。
Room时SQLiteHelper上的一层抽象,这次用SQLiteHelper的功能去查看room的各个功能是如何实现的。
-
打开数据库:RoomDatabase.mOpenHelper 在RoomDatabase init时 实现类的createOpenHelper创建
FrameworkSQLiteOpenHelper.getWritableDatabase, FrameworkSQLiteOpenHelper生成一个委托类OpenHelper是SQLiteOpenHelper的子类。后面就是通过OpenHelper调用SQLiteDatabase.getWritableDatabase()打开连接数据库。
-
看一下room 创建更新表的逻辑 :OpenHelper.onCreate->RoomOpenHelper.onCreate。room回自己创建一个room_master_table,room回根据Entity在RoomOpenHelper实现类createAllTables生成创建表的逻辑。后面执行databaseBuilder.addCallback加入的Callback回调。更新:升级
-
增,删,改。委托于原来的方法流程SQLiteDatabase 创建一个SQLiteStatement预处理SQL语句,绑定查询参数,SQLiteStatement去查询(session->connectionpool->connection)。FrameworkSQLiteDatabase委托于SQLiteDatabase,FrameworkSQLiteStatement委托于SQLiteStatement
-
查询。RoomSQLiteQuery(用于缓存绑定参数)和SQLiteQuery没委托关系。查询流程见SQLiteOpenHelper
SupportSQLiteOpenHelper
用于映射SQLiteOpenHelper行为的一个接口。
FrameworkSQLiteDatabase:委托类委托于SQLiteDatabase
FrameworkSQLiteStatement:委托于SQLiteStatement
createOpenHelper
@Override
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
@Override
public void createAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("CREATE TABLE IF NOT EXISTS `tb_debug` (`id` INTEGER NOT NULL, `content` TEXT, PRIMARY KEY(`id`))");
_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, \"11aae594046709aa50c44370e237d950\")");
}
@Override
public void dropAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("DROP TABLE IF EXISTS `tb_debug`");
}
@Override
protected void onCreate(SupportSQLiteDatabase _db) {
if (mCallbacks != null) {
for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
mCallbacks.get(_i).onCreate(_db);
}
}
}
@Override
public void onOpen(SupportSQLiteDatabase _db) {
mDatabase = _db;
internalInitInvalidationTracker(_db);
if (mCallbacks != null) {
for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
mCallbacks.get(_i).onOpen(_db);
}
}
}
@Override
protected void validateMigration(SupportSQLiteDatabase _db) {
final HashMap<String, TableInfo.Column> _columnsTbDebug = new HashMap<String, TableInfo.Column>(2);
_columnsTbDebug.put("id", new TableInfo.Column("id", "INTEGER", true, 1));
_columnsTbDebug.put("content", new TableInfo.Column("content", "TEXT", false, 0));
final HashSet<TableInfo.ForeignKey> _foreignKeysTbDebug = new HashSet<TableInfo.ForeignKey>(0);
final HashSet<TableInfo.Index> _indicesTbDebug = new HashSet<TableInfo.Index>(0);
final TableInfo _infoTbDebug = new TableInfo("tb_debug", _columnsTbDebug, _foreignKeysTbDebug, _indicesTbDebug);
final TableInfo _existingTbDebug = TableInfo.read(_db, "tb_debug");
if (! _infoTbDebug.equals(_existingTbDebug)) {
throw new IllegalStateException("Migration didn't properly handle tb_debug(com.ruixue.widelypaycashier.debug.local.DebugEntity).\n"
+ " Expected:\n" + _infoTbDebug + "\n"
+ " Found:\n" + _existingTbDebug);
}
}
}, "11aae594046709aa50c44370e237d950", "1382025f9c31fbd26fd351ed296fd598");
final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
.name(configuration.name)
.callback(_openCallback)
.build();
final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
return _helper;
}
SupportSQLiteOpenHelper.Callback:处理连接数据库生命周期的个个时间,类似SQLiteOpenHelper功能
SupportSQLiteOpenHelper.Configuration: 用于SupportSQLiteOpenHelper.Factory创建helper的一个配置文件
sqliteOpenHelperFactory 在RoomDatabase build时传入没有默认为FrameworkSQLiteOpenHelperFactory
FrameworkSQLiteOpenHelperFactory:用于创建一个FrameworkSQLiteOpenHelper。最终我们得到是一个
FrameworkSQLiteOpenHelper
升级
@Nullable
public List<Migration> findMigrationPath(int start, int end) {
if (start == end) {
return Collections.emptyList();
}
boolean migrateUp = end > start;
List<Migration> result = new ArrayList<>();
return findUpMigrationPath(result, migrateUp, start, end);
}
private List<Migration> findUpMigrationPath(List<Migration> result, boolean upgrade,
int start, int end) {
final int searchDirection = upgrade ? -1 : 1;
while (upgrade ? start < end : start > end) {
SparseArrayCompat<Migration> targetNodes = mMigrations.get(start);
if (targetNodes == null) {
return null;
}
// keys are ordered so we can start searching from one end of them.
final int size = targetNodes.size();
final int firstIndex;
final int lastIndex;
if (upgrade) {
firstIndex = size - 1;
lastIndex = -1;
} else {
firstIndex = 0;
lastIndex = size;
}
boolean found = false;
for (int i = firstIndex; i != lastIndex; i += searchDirection) {
final int targetVersion = targetNodes.keyAt(i);
final boolean shouldAddToPath;
if (upgrade) {
shouldAddToPath = targetVersion <= end && targetVersion > start;
} else {
shouldAddToPath = targetVersion >= end && targetVersion < start;
}
if (shouldAddToPath) {
result.add(targetNodes.valueAt(i));
start = targetVersion;
found = true;
break;
}
}
if (!found) {
return null;
}
}
return result;
}
public void addMigrations(@NonNull Migration... migrations) {
for (Migration migration : migrations) {
addMigration(migration);
}
}
private void addMigration(Migration migration) {
final int start = migration.startVersion;
final int end = migration.endVersion;
SparseArrayCompat<Migration> targetMap = mMigrations.get(start);
if (targetMap == null) {
targetMap = new SparseArrayCompat<>();
mMigrations.put(start, targetMap);
}
Migration existing = targetMap.get(end);
if (existing != null) {
Log.w(Room.LOG_TAG, "Overriding migration " + existing + " with " + migration);
}
targetMap.append(end, migration);
}
room缓存Migration结构是一个嵌套SparseArrayCompat<SparseArrayCompat<Migration>>,第一层对象startVesion作为key,endVersion的列表作为值。第二层endVersion为key,Migration为值。
寻找合适的升级Migration逻辑。
升级:数据库当前版本为key找到对应的SparseArrayCompat<Migration> targetNodes ,从targetNodes最后位置往前寻找,找到符合在当前版本和指定升级版本之间的版本。匹配返回执行Migration逻辑。
这样做之后我们就不是必须对应上个版本去做改动,我们只要改动两个版本之间的变动就好了,可以直接从1到3,而不必1到2到3.如果按照以前的逻辑Migration(1,2),Migration(2,3)这样添加两个就好了
总结:
- Dao根据具用户定义行为生成Dao_Impl与数据库交互。
- RoomDatabase 为Dao提供访问数据库方法
- FrameworkSQLiteOpenHelper,SQLiteOpenHelper委托类供RoomDatabase使用
- FrameworkSQLiteDatabase,SQLiteDatabase委托类通过SupportSQLiteOpenHelper获取
- FrameworkSQLiteStatement,SQLiteStatement委托类
流程Dao->RoomDatabase->FrameworkSQLiteOpenHelper->SQLiteOpenHelper->FrameworkSQLiteDatabase->SQLiteDatabase->FrameworkSQLiteStatement->SQLiteStatement->SQLiteSession->SQLiteConnectionPool->SQLiteConnection->Native。
room运行时代码还是比较简单的,烦的应该是compiler中生存代码的逻辑。