Room框架是Google开发,简化我们数据库操作的代码编写的框架。
下面我们先展示一个范例。这个需求是班次历史记录,搜索过的线路的集合。一条线路包含出发站和到达站的信息。
TrainLineDbBean是我们的业务bean也就是线路信息。 在room 框架中会添加注解使之成为一张表的抽象。Entity声明了表名为 “TRAIN_LINE”。新建索引名为"line"取{“startStationCode”,“endStationCode”}两个字段,且唯一。ColumnInfo可以命名该变量对应表中的字段名。声明一个id主键字段自增。
@Entity(tableName = "TRAIN_LINE", indices = {@Index(name = "line", value = {"startStationCode","endStationCode"}, unique = true)})
public class TrainLineDbBean {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = "startStationCode")
private String startStationCode;
@ColumnInfo(name = "startStationName")
private String startStationName;
@ColumnInfo(name = "startStationType")
private String startStationType;
@ColumnInfo(name = "endStationCode")
private String endStationCode;
@ColumnInfo(name = "endStationName")
private String endStationName;
@ColumnInfo(name = "endStationType")
private String endStationType;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getStartStationCode() {
return startStationCode;
}
public void setStartStationCode(String startStationCode) {
this.startStationCode = startStationCode;
}
public String getStartStationName() {
return startStationName;
}
public void setStartStationName(String startStationName) {
this.startStationName = startStationName;
}
public String getStartStationType() {
return startStationType;
}
public void setStartStationType(String startStationType) {
this.startStationType = startStationType;
}
public String getEndStationCode() {
return endStationCode;
}
public void setEndStationCode(String endStationCode) {
this.endStationCode = endStationCode;
}
public String getEndStationName() {
return endStationName;
}
public void setEndStationName(String endStationName) {
this.endStationName = endStationName;
}
public String getEndStationType() {
return endStationType;
}
public void setEndStationType(String endStationType) {
this.endStationType = endStationType;
}
}
有了表,当然还要有对应的CURD,新建一个接口类用@Dao注解。以后我们知道这个注解会被annotationProcessor生成一个实现类。这个接口主要声明操作,每个方法上都包含对应着增删改查的SQL语句。delete没有应该是框架根据已有的信息帮我们做默认操作。
@Dao
public interface TrainLineDao {
@Query("SELECT * FROM TRAIN_LINE ORDER BY id DESC LIMIT 5")
List<TrainLineDbBean> getAll();
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(TrainLineDbBean... trainLineDbBeans);
@Delete
void delete(TrainLineDbBean... trainLineDbBeans);
@Query("delete from TRAIN_LINE")
int deleteAll();
}
抽象类AppDatabase 继承于RoomDatabase类,这个类建议包装成单例使用。@Database声明了数据库包含的表类。版本号,以及是否导出Schema,就是那些建表语句。Room.databaseBuilder三个参数,Context,RoomDatabase子类,数据库名。allowMainThreadQueries允许主线程操作。
@Database(entities = {TrainCityDbBean.class,TrainLineDbBean.class},version = 1,exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
public static final String DataBase = "DataBase";
public abstract TrainCityDao trainCityDao();
public abstract TrainLineDao trainLineDao();
private static class MYHOLder{
public static final AppDatabase appDatabase =Room.databaseBuilder(CommonData.getAppContext(),
AppDatabase.class, DataBase).allowMainThreadQueries().build();
}
public static AppDatabase getInstance(){
return MYHOLder.appDatabase;
}
}
在build->generated->source->apt->中出现了三个生成类
三个类就完成一个数据库的创建,表操作。 如再加表,只需要写一个表类加一个Dao,在AppDatabase中配置一下,非常方便,下面我们来看看Google如何封装复杂,提供给我们方便。
首先我们来看RoomDatabase类。里面一个JournalMode日志类型枚举,Builder模式类。MigrationContainer迁移容器类。
在数据类创建和打开的时候调用
public abstract static class Callback {
public void onCreate(@NonNull SupportSQLiteDatabase db) {
}
public void onOpen(@NonNull SupportSQLiteDatabase db) {
}
}
。。。
Builder
public Builder<T> addCallback(@NonNull Callback callback) {
if (mCallbacks == null) {
mCallbacks = new ArrayList<>();
}
mCallbacks.add(callback);
return this;
}
数据库升级操作,从startVersion到endVersion,在抽象方法中migrate用户自己实现。
public abstract class Migration {
public final int startVersion;
public final int endVersion;
/**
* Creates a new migration between {@code startVersion} and {@code endVersion}.
*
* @param startVersion The start version of the database.
* @param endVersion The end version of the database after this migration is applied.
*/
public Migration(int startVersion, int endVersion) {
this.startVersion = startVersion;
this.endVersion = endVersion;
}
/**
* Should run the necessary migrations.
* <p>
* This class cannot access any generated Dao in this method.
* <p>
* This method is already called inside a transaction and that transaction might actually be a
* composite transaction of all necessary {@code Migration}s.
*
* @param database The database instance
*/
public abstract void migrate(@NonNull SupportSQLiteDatabase database);
}
Builder有个MigrationContainer对象,通过addMigrations方法,添加版本变迁的操作,操作支持,升级,降级,比如。start(3) ->end(5) , start(5)->end(3). Migration上面分析过就是升降级要做的操作,升级时优先匹配start, end. 即MigrationContainer中包含start(3)->end(5).不会使用start(3)->end(4) & start(4)->end(5).
public static class Builder<T extends RoomDatabase> {
private final MigrationContainer mMigrationContainer;
@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;
}
MigrationContainer类中的容器是两层SparseArrayCompat嵌套。SparseArrayCompat是以int为key的容器。比如,用start(3) 去寻找,可以找到一个以 3 为startVersion的操作集合,可能有3->4,3->5…
public static class MigrationContainer {
private SparseArrayCompat<SparseArrayCompat<Migration>> mMigrations =
new SparseArrayCompat<>();
/**
* Adds the given migrations to the list of available migrations. If 2 migrations have the
* same start-end versions, the latter migration overrides the previous one.
*
* @param migrations List of available migrations.
*/
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);
}
当触发数据库版本变更时,会去查找对应的操作。变更版本相同,返回空操作。findUpMigrationPath这个函数根据start和end的大小来区分升级还是降级。如果是跨级操作,那优先选择一步到位,找到后就退出了while循环,否则会找到逐级变更的操作。
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;
}
再来看看Builder.build方法,判断mContext是否存在,是否有RoomDatabase继承类,是否mMigrationStartAndEndVersions中不包含不需要变更的起始或结束版本。DatabaseConfiguration结合builder的设置,Room.getGeneratedImplementation得到db调用init方法。初始化完成返回。
public T build() {
//noinspection ConstantConditions
if (mContext == null) {
throw new IllegalArgumentException("Cannot provide null context for the database.");
}
//noinspection ConstantConditions
if (mDatabaseClass == null) {
throw new IllegalArgumentException("Must provide an abstract class that"
+ " extends RoomDatabase");
}
if (mMigrationStartAndEndVersions != null && mMigrationsNotRequiredFrom != null) {
for (Integer version : mMigrationStartAndEndVersions) {
if (mMigrationsNotRequiredFrom.contains(version)) {
throw new IllegalArgumentException(
"Inconsistency detected. A Migration was supplied to "
+ "addMigration(Migration... migrations) that has a start "
+ "or end version equal to a start version supplied to "
+ "fallbackToDestructiveMigrationFrom(int... "
+ "startVersions). Start version: "
+ version);
}
}
}
if (mFactory == null) {
mFactory = new FrameworkSQLiteOpenHelperFactory();
}
DatabaseConfiguration configuration =
new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
mCallbacks, mAllowMainThreadQueries,
mJournalMode.resolve(mContext),
mRequireMigration, mMigrationsNotRequiredFrom);
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
db.init(configuration);
return db;
}
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);就是用类名去点转下划线+"_Impl",Class.forName取出注解处理器生成的类。利用反射aClass.newInstance() 得出实例。
private static final String DB_IMPL_SUFFIX = "_Impl";
public class Room {
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) {
throw new RuntimeException("cannot find implementation for "
+ klass.getCanonicalName() + ". " + implName + " does not exist");
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access the constructor"
+ klass.getCanonicalName());
} catch (InstantiationException e) {
throw new RuntimeException("Failed to create an instance of "
+ klass.getCanonicalName());
}
}
AppDatabase_Impl 生成类可以看出来实现了那些我们操作数据库繁杂的代码。
public class AppDatabase_Impl extends AppDatabase {
private volatile TrainCityDao _trainCityDao;
private volatile TrainLineDao _trainLineDao;
@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 `TRAIN_CITY` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `stationCode` TEXT, `stationName` TEXT, `stationType` TEXT)");
_db.execSQL("CREATE UNIQUE INDEX `name` ON `TRAIN_CITY` (`stationCode`)");
_db.execSQL("CREATE TABLE IF NOT EXISTS `TRAIN_LINE` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `startStationCode` TEXT, `startStationName` TEXT, `startStationType` TEXT, `endStationCode` TEXT, `endStationName` TEXT, `endStationType` TEXT)");
_db.execSQL("CREATE UNIQUE INDEX `line` ON `TRAIN_LINE` (`startStationCode`, `endStationCode`)");
_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, \"d86bfdaa164f5dff4c0952c306310ca8\")");
}
@Override
public void dropAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("DROP TABLE IF EXISTS `TRAIN_CITY`");
_db.execSQL("DROP TABLE IF EXISTS `TRAIN_LINE`");
}
@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> _columnsTRAINCITY = new HashMap<String, TableInfo.Column>(4);
_columnsTRAINCITY.put("id", new TableInfo.Column("id", "INTEGER", true, 1));
_columnsTRAINCITY.put("stationCode", new TableInfo.Column("stationCode", "TEXT", false, 0));
_columnsTRAINCITY.put("stationName", new TableInfo.Column("stationName", "TEXT", false, 0));
_columnsTRAINCITY.put("stationType", new TableInfo.Column("stationType", "TEXT", false, 0));
final HashSet<TableInfo.ForeignKey> _foreignKeysTRAINCITY = new HashSet<TableInfo.ForeignKey>(0);
final HashSet<TableInfo.Index> _indicesTRAINCITY = new HashSet<TableInfo.Index>(1);
_indicesTRAINCITY.add(new TableInfo.Index("name", true, Arrays.asList("stationCode")));
final TableInfo _infoTRAINCITY = new TableInfo("TRAIN_CITY", _columnsTRAINCITY, _foreignKeysTRAINCITY, _indicesTRAINCITY);
final TableInfo _existingTRAINCITY = TableInfo.read(_db, "TRAIN_CITY");
if (! _infoTRAINCITY.equals(_existingTRAINCITY)) {
throw new IllegalStateException("Migration didn't properly handle TRAIN_CITY(com.tts.trip.train.db.TrainCityDbBean).\n"
+ " Expected:\n" + _infoTRAINCITY + "\n"
+ " Found:\n" + _existingTRAINCITY);
}
final HashMap<String, TableInfo.Column> _columnsTRAINLINE = new HashMap<String, TableInfo.Column>(7);
_columnsTRAINLINE.put("id", new TableInfo.Column("id", "INTEGER", true, 1));
_columnsTRAINLINE.put("startStationCode", new TableInfo.Column("startStationCode", "TEXT", false, 0));
_columnsTRAINLINE.put("startStationName", new TableInfo.Column("startStationName", "TEXT", false, 0));
_columnsTRAINLINE.put("startStationType", new TableInfo.Column("startStationType", "TEXT", false, 0));
_columnsTRAINLINE.put("endStationCode", new TableInfo.Column("endStationCode", "TEXT", false, 0));
_columnsTRAINLINE.put("endStationName", new TableInfo.Column("endStationName", "TEXT", false, 0));
_columnsTRAINLINE.put("endStationType", new TableInfo.Column("endStationType", "TEXT", false, 0));
final HashSet<TableInfo.ForeignKey> _foreignKeysTRAINLINE = new HashSet<TableInfo.ForeignKey>(0);
final HashSet<TableInfo.Index> _indicesTRAINLINE = new HashSet<TableInfo.Index>(1);
_indicesTRAINLINE.add(new TableInfo.Index("line", true, Arrays.asList("startStationCode","endStationCode")));
final TableInfo _infoTRAINLINE = new TableInfo("TRAIN_LINE", _columnsTRAINLINE, _foreignKeysTRAINLINE, _indicesTRAINLINE);
final TableInfo _existingTRAINLINE = TableInfo.read(_db, "TRAIN_LINE");
if (! _infoTRAINLINE.equals(_existingTRAINLINE)) {
throw new IllegalStateException("Migration didn't properly handle TRAIN_LINE(com.tts.trip.train.db.TrainLineDbBean).\n"
+ " Expected:\n" + _infoTRAINLINE + "\n"
+ " Found:\n" + _existingTRAINLINE);
}
}
}, "d86bfdaa164f5dff4c0952c306310ca8", "7b3ff187eec53ad4a24ee9e6339a0057");
final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
.name(configuration.name)
.callback(_openCallback)
.build();
final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
return _helper;
}
@Override
protected InvalidationTracker createInvalidationTracker() {
return new InvalidationTracker(this, "TRAIN_CITY","TRAIN_LINE");
}
@Override
public void clearAllTables() {
super.assertNotMainThread();
final SupportSQLiteDatabase _db = super.getOpenHelper().getWritableDatabase();
try {
super.beginTransaction();
_db.execSQL("DELETE FROM `TRAIN_CITY`");
_db.execSQL("DELETE FROM `TRAIN_LINE`");
super.setTransactionSuccessful();
} finally {
super.endTransaction();
_db.query("PRAGMA wal_checkpoint(FULL)").close();
if (!_db.inTransaction()) {
_db.execSQL("VACUUM");
}
}
}
@Override
public TrainCityDao trainCityDao() {
if (_trainCityDao != null) {
return _trainCityDao;
} else {
synchronized(this) {
if(_trainCityDao == null) {
_trainCityDao = new TrainCityDao_Impl(this);
}
return _trainCityDao;
}
}
}
@Override
public TrainLineDao trainLineDao() {
if (_trainLineDao != null) {
return _trainLineDao;
} else {
synchronized(this) {
if(_trainLineDao == null) {
_trainLineDao = new TrainLineDao_Impl(this);
}
return _trainLineDao;
}
}
}
}