介紹
Jetpack 的ROOM就是在android支持的SQLite的基礎(chǔ)上進(jìn)行了一個(gè)抽象的封裝,這樣方便用戶更好的利用SQLite的強(qiáng)大功能篙梢,進(jìn)行更加強(qiáng)健的數(shù)據(jù)庫(kù)訪問機(jī)制腌乡。
ROOM 是以注解的方式在編譯期幫助我們生成很多代碼素征,這樣就可以將我們的重心放在業(yè)務(wù)邏輯的處理上了昙啄。
筆者對(duì)ROOM的理解:
通過注解的方式將數(shù)據(jù)庫(kù)操作整慎、表操作等比較繁瑣的工作交給注解處理器工作狭吼,我們只需要?jiǎng)?chuàng)建數(shù)據(jù)模型和對(duì)數(shù)據(jù)庫(kù)的操作即可赠幕。
ROOM的三大組件
1) DataBase: 他所標(biāo)記的類就是一個(gè)數(shù)據(jù)庫(kù)類,
2) Entity:表示數(shù)據(jù)庫(kù)中的表
3) Dao:表示一個(gè)可以對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作(增刪改查)的接口規(guī)范(不包括對(duì)數(shù)據(jù)庫(kù)的創(chuàng)建)
基本使用
1狱从、引入依賴庫(kù)
def room_version = "2.3.0"
compile "android.arch.persistence.room:runtime:$room_version"
kapt "android.arch.persistence.room:compiler:$room_version"
//如果您已遷移到androidx
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
2膨蛮、數(shù)據(jù)庫(kù)
數(shù)據(jù)庫(kù)的操作不允許允許在主線程中,但是可以通過配置使其在主線程中允許(allowMainThreadQueries)
- 數(shù)據(jù)庫(kù)使用@Database注解季研,必須繼承RoomDatabase 敞葛,而且是一個(gè)抽象類,數(shù)據(jù)庫(kù)用于提供表操作Dao實(shí)例對(duì)象的獲取与涡,這里都是抽象方法惹谐,編譯時(shí),APT會(huì)生成其相關(guān)的實(shí)現(xiàn)類
- entities 是指數(shù)據(jù)庫(kù)中的表的類class,如果有多個(gè)表递沪,則用逗號(hào)隔開即可
- version 代表數(shù)據(jù)庫(kù)的版本號(hào)豺鼻,用于數(shù)據(jù)庫(kù)升級(jí),遷移等使用
- exportSchema 需要指明是否導(dǎo)出到文件中去
@Database(entities = {Student.class, FamilyAddress.class}, version = 1, exportSchema = true)
public abstract class StudentDataBase extends RoomDatabase {
private static StudentDataBase mInstance;
//單例
public static synchronized StudentDataBase getInstance(Context context) {
if (mInstance == null) {
synchronized (StudentDataBase.class) {
if (mInstance == null) {
mInstance = Room.databaseBuilder(context.getApplicationContext(),//應(yīng)用上下文
StudentDataBase.class,//數(shù)據(jù)庫(kù)類型
"StudentDataBse")//數(shù)據(jù)庫(kù)的名字
.allowMainThreadQueries()//允許在主線程中允許
.build();
}
}
}
return mInstance;
}
//獲取dao的實(shí)例對(duì)象
public abstract StudentDao getStudentDao();
}
2.1款慨、數(shù)據(jù)庫(kù)強(qiáng)制升級(jí)(不建議)
在創(chuàng)建數(shù)據(jù)庫(kù)實(shí)例對(duì)象的build()之前儒飒,調(diào)用fallbackToDestructiveMigration(),但是這種方式不建議使用,因?yàn)檫@種強(qiáng)制升級(jí)會(huì)導(dǎo)致數(shù)據(jù)庫(kù)的結(jié)構(gòu)發(fā)生變化檩奠,并且會(huì)導(dǎo)致數(shù)據(jù)庫(kù)中的數(shù)據(jù)全部丟失桩了。
public static synchronized StudentDataBase getInstance(Context context) {
if (mInstance == null) {
synchronized (StudentDataBase.class) {
if (mInstance == null) {
mInstance = Room.databaseBuilder(context.getApplicationContext(),
StudentDataBase.class,
"StudentDataBse")
.fallbackToDestructiveMigration()//強(qiáng)制升級(jí)
.build();
}
}
}
return mInstance;
}
2.2附帽、MIGRATION保留數(shù)據(jù)升級(jí)
1)創(chuàng)建MIGRATION變量(需要依次傳入兩個(gè)版本的版本號(hào))
在給表增加或者修改列信息時(shí),同時(shí)也需要將@Entity對(duì)應(yīng)的實(shí)體進(jìn)行修改
private static Migration MIGRATION_1_to_2 = new Migration(1,2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
//這里通過SQL語(yǔ)句來進(jìn)行升級(jí),下面是給表增加列信息的升級(jí)
database.execSQL("ALTER TABLE Student ADD COLUMN sex CHAR NOT NULL DEFAULT 'm'");
}
};
- 添加MIGRATION變量
public static synchronized StudentDataBase getInstance(Context context) {
if (mInstance == null) {
synchronized (StudentDataBase.class) {
if (mInstance == null) {
mInstance = Room.databaseBuilder(context.getApplicationContext(),
StudentDataBase.class,
"StudentDataBse")
.addMigrations(MIGRATION_1_to_2)
.build();
}
}
}
return mInstance;
}
3)修改原數(shù)據(jù)庫(kù)版本信息
//這里需要將version的值由1 修改到 2
@Database(entities = {Student.class, FamilyAddress.class}, version = 2, exportSchema = true)
2.3井誉、 數(shù)據(jù)庫(kù)遷移
數(shù)據(jù)庫(kù)遷移的思路:
創(chuàng)建新的數(shù)據(jù)庫(kù)蕉扮,并將表結(jié)構(gòu)及其數(shù)據(jù)復(fù)制到新的數(shù)據(jù)庫(kù)中去,刪除原數(shù)據(jù)庫(kù)颗圣,重命名新數(shù)據(jù)庫(kù)為原數(shù)據(jù)庫(kù)名喳钟,這樣就達(dá)到數(shù)據(jù)庫(kù)遷移的目標(biāo)了。
3在岂、表結(jié)構(gòu)(實(shí)體)
- @Entity表示數(shù)據(jù)庫(kù)的表結(jié)構(gòu);
- @PrimaryKey 表示是一個(gè)主鍵奔则,參數(shù)autoGenerate 為true時(shí),表示主鍵是自動(dòng)生成的蔽午,這里不能給默認(rèn)值;
- @ColumnInfo 表示表中的一個(gè)屬性列信息易茬,參數(shù)name 就是標(biāo)的列的名稱,與定義的變量名一致;
- @Embedded 對(duì)象注解
- @Ignore 忽略注解及老,用在方法或字段上抽莱;
@Entity
public class Student {
@PrimaryKey(autoGenerate = true)
private int id;
@Ignore
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
@ColumnInfo(name = "name")
private String name;
@ColumnInfo(name = "age")
private int age;
public Student(String name, int age, char sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
@ColumnInfo(name = "sex")
private char sex;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
@Entity
public class Teacher {
public int getTeacherId() {
return teacherId;
}
public void setTeacherId(int teacherId) {
this.teacherId = teacherId;
}
@PrimaryKey(autoGenerate = true)
private int teacherId;
@ColumnInfo(name = "name")
private String name;
public Teacher(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Teacher{" +
", name='" + name + '\'' +
'}';
}
public void setName(String name) {
this.name = name;
}
}
4、Dao層
Dao層其實(shí)就是定義一套操作數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn)(增刪改查等)骄恶,這樣編譯器編譯生成Dao層的實(shí)例類食铐,對(duì)數(shù)據(jù)庫(kù)的操作就可以直接使用Dao層對(duì)象操作了。
4.1叠蝇、 使用@Dao注解來表示Dao層璃岳,且是一個(gè)接口
@Dao
public interface StudentDao {
//插入
@Insert
void insert(Student student);
//刪除
@Delete
void delete(Student student);
//修改
@Update
void update(Student student);
//查詢
@Query("select * from Student")
List<Student> getAll();
}
4.2、 條件查詢:
1)方法的參數(shù)name 就是在SQL語(yǔ)句like :冒號(hào)后面的參數(shù)name
2)多個(gè)參數(shù)查詢時(shí)使用and來連接參數(shù)悔捶,每個(gè)參數(shù)使用like+冒號(hào)+參數(shù)名
3)集合查詢使用 in (: 參數(shù)名)
@Query("select * from Student where name like :name and age like :age")
Student queryStudent(String name,int age);
//集合查詢
@Query("select * from Student where name in (:userNames)")
List<Student> queryStudents(List<String> userNames);
4.3、字段查詢
查詢的結(jié)果需要重新定義一個(gè)數(shù)據(jù)結(jié)構(gòu)來存放单芜。
@Query("select name ,age From Student")
List<PartFieldResult> queryFields();
4.4蜕该、多表查詢
1) 多表查詢,如果表中的有字段是重復(fù)時(shí)洲鸠,需要使用as來指定對(duì)應(yīng)關(guān)系
2) 多表查詢的結(jié)果需要保存在一個(gè)新的實(shí)體中
@Query("select Student.name as name ,Student.age as age ,Teacher.name as teacherName from student,teacher")
List<MutilTableResult> queryMutilTable();
5堂淡、使用:
5.1、獲取數(shù)據(jù)庫(kù)Dao實(shí)例對(duì)象
StudentDao studentDB;
RoomThread roomThread;
studentDB = StudentDataBase.getInstance(RoomMainActivity.this).getStudentDao();
roomThread = new RoomThread();
5.2扒腕、創(chuàng)建子線程
class RoomThread extends Thread {
@Override
public void run() {
switch (type) {
case 0:
insertDataBase();
break;
case 1:
queryAll();
break;
case 2:
querySingle();
break;
case 3:
queryMutil();
break;
case 4:
queryMutilTable();
break;
case 5:
queryFields();
break;
case 6:
insertDataBase();
break;
default:
break;
}
}
}
5.3绢淀、調(diào)用Dao的方法操作數(shù)據(jù)庫(kù)
private void queryFields() {
List<PartFieldResult> partFieldResults = studentDB.queryFields();
Log.e(TAG, "字段查詢:\n" + partFieldResults.toString());
Message message = new Message();
message.obj = "\n\n\n字段查詢:\n" + partFieldResults.toString();
mHandler.sendMessage(message);
}
private void queryMutilTable() {
List<MutilTableResult> queryMutilTable = studentDB.queryMutilTable();
Log.e(TAG, "多表查詢:\n" + queryMutilTable.toString());
Message message = new Message();
message.obj = "\n\n\n多表查詢:\n" + queryMutilTable.toString();
mHandler.sendMessage(message);
}
void queryMutil() {
List<String> temp = new ArrayList<String>();
temp.add("張三");
temp.add("王五");
List<Student> students = studentDB.queryStudents(temp);
Log.e(TAG, "多條件查詢:\n" + students.toString());
Message message = new Message();
message.obj = "\n\n\n多條件查詢:\n" + students.toString();
mHandler.sendMessage(message);
}
void querySingle() {
Student student = studentDB.queryStudent("馬六",15);
Log.e(TAG, "單條件查詢:\n" + student.toString());
Message message = new Message();
message.obj = "\n\n\n單條件查詢:\n" + student.toString();
mHandler.sendMessage(message);
}
void queryAll() {
List<Student> all = studentDB.getAll();
Log.e(TAG, "查詢?nèi)?\n" + all);
Message message = new Message();
message.obj = "查詢?nèi)?\n" + all;
mHandler.sendMessage(message);
}
void insertDataBase() {
studentDB.insert(new Student("張三", 12));
studentDB.insert(new Student("李四", 13));
studentDB.insert(new Student("王五", 14));
studentDB.insert(new Student("馬六", 15));
}
原理分析
在編譯期通過注解處理器生成對(duì)應(yīng)的Dao層和抽象數(shù)據(jù)庫(kù)的實(shí)例類,
當(dāng)開始調(diào)用Room的build()時(shí)瘾腰,通過反射技術(shù)反射數(shù)據(jù)庫(kù)實(shí)現(xiàn)類的實(shí)例對(duì)象皆的,同時(shí)創(chuàng)建對(duì)應(yīng)的數(shù)據(jù)庫(kù)表;
當(dāng)通過數(shù)據(jù)庫(kù)實(shí)例對(duì)象獲取Dao的實(shí)例對(duì)象時(shí)蹋盆,就會(huì)進(jìn)行數(shù)據(jù)庫(kù)表結(jié)構(gòu)的初始化工作费薄,
至此數(shù)據(jù)庫(kù)及表結(jié)構(gòu)就都已經(jīng)創(chuàng)建好了硝全,接下來通過Dao實(shí)例對(duì)象調(diào)用對(duì)應(yīng)的接口標(biāo)準(zhǔn)進(jìn)行相應(yīng)的數(shù)據(jù)庫(kù)操作。
1楞抡、RoomDataBase類的build()
工作:新建創(chuàng)建數(shù)據(jù)庫(kù)的工廠伟众,準(zhǔn)備數(shù)據(jù)庫(kù)需要的相關(guān)配置信息等。
public T build() {
//下面都是創(chuàng)建對(duì)應(yīng)的創(chuàng)建數(shù)據(jù)庫(kù)的工廠
SupportSQLiteOpenHelper.Factory factory;
if (mFactory == null) {
factory = new FrameworkSQLiteOpenHelperFactory();
} else {
factory = mFactory;
}
if (mAutoCloseTimeout > 0) {
factory = new AutoClosingRoomOpenHelperFactory(factory, autoCloser);
}
if (mCopyFromAssetPath != null
|| mCopyFromFile != null
|| mCopyFromInputStream != null) {
factory = new SQLiteCopyOpenHelperFactory(mCopyFromAssetPath, mCopyFromFile,
mCopyFromInputStream, factory);
}
if (mQueryCallback != null) {
factory = new QueryInterceptorOpenHelperFactory(factory, mQueryCallback,
mQueryCallbackExecutor);
}
//配置創(chuàng)建數(shù)據(jù)庫(kù)對(duì)應(yīng)的參數(shù)
DatabaseConfiguration configuration =
new DatabaseConfiguration(
mContext,
mName,
factory,
mMigrationContainer,
mCallbacks,
mAllowMainThreadQueries,
mJournalMode.resolve(mContext),
mQueryExecutor,
mTransactionExecutor,
mMultiInstanceInvalidation,
mRequireMigration,
mAllowDestructiveMigrationOnDowngrade,
mMigrationsNotRequiredFrom,
mCopyFromAssetPath,
mCopyFromFile,
mCopyFromInputStream,
mPrepackagedDatabaseCallback,
mTypeConverters);
//下面是反射獲取生成的數(shù)據(jù)庫(kù)實(shí)例
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
//對(duì)數(shù)據(jù)庫(kù)進(jìn)行配置
db.init(configuration);
return db;
}
}
2召廷、Room類的getGeneratedImplementation()
通過反射技術(shù)反射出來數(shù)據(jù)庫(kù)實(shí)現(xiàn)類的實(shí)例對(duì)象
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 {
//反射獲取抽象數(shù)據(jù)庫(kù)類的實(shí)例對(duì)象
final String fullClassName = fullPackage.isEmpty()
? implName
: fullPackage + "." + implName;
@SuppressWarnings("unchecked")
final Class<T> aClass = (Class<T>) Class.forName(
fullClassName, true, klass.getClassLoader());
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());
}
}
3凳厢、Dao層的實(shí)現(xiàn)類
實(shí)現(xiàn)數(shù)據(jù)庫(kù)的操作:表結(jié)構(gòu)初始化、數(shù)據(jù)庫(kù)操作SQL調(diào)用執(zhí)行等竞慢。
public final class StudentDao_Impl implements StudentDao {
private final RoomDatabase __db;
private final EntityInsertionAdapter<Student> __insertionAdapterOfStudent;
private final EntityDeletionOrUpdateAdapter<Student> __deletionAdapterOfStudent;
private final EntityDeletionOrUpdateAdapter<Student> __updateAdapterOfStudent;
public StudentDao_Impl(RoomDatabase __db) {
this.__db = __db;
this.__insertionAdapterOfStudent = new EntityInsertionAdapter<Student>(__db) {
@Override
public String createQuery() {
return "INSERT OR ABORT INTO `Student` (`id`,`teachId`,`name`,`age`,`sex`) VALUES (nullif(?, 0),?,?,?,?)";
}
@Override
public void bind(SupportSQLiteStatement stmt, Student value) {
stmt.bindLong(1, value.getId());
stmt.bindLong(2, value.getTeachId());
if (value.getName() == null) {
stmt.bindNull(3);
} else {
stmt.bindString(3, value.getName());
}
stmt.bindLong(4, value.getAge());
stmt.bindLong(5, value.getSex());
}
};
}
@Override
public void insert(final Student student) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__insertionAdapterOfStudent.insert(student);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
@Override
public void delete(final Student student) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__deletionAdapterOfStudent.handle(student);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
@Override
public void update(final Student student) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__updateAdapterOfStudent.handle(student);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
//其他的方法省略
}
4先紫、數(shù)據(jù)庫(kù)抽象類的實(shí)現(xiàn)類
1)創(chuàng)建數(shù)據(jù)庫(kù),包括數(shù)據(jù)庫(kù)中的表
2)創(chuàng)建Dao層的實(shí)例對(duì)象
public final class StudentDataBase_Impl extends StudentDataBase {
private volatile StudentDao _studentDao;
@Override
public StudentDao getStudentDao() {
if (_studentDao != null) {
return _studentDao;
} else {
synchronized(this) {
if(_studentDao == null) {
_studentDao = new StudentDao_Impl(this);
}
return _studentDao;
}
}
}
}
總結(jié)
1梗顺、技術(shù)點(diǎn):
反射+APT+單例模式+工廠模式
2泡孩、ROOM 坑:
在使用Room時(shí),會(huì)出現(xiàn)如下的錯(cuò)誤:
java.lang.RuntimeException: cannot find implementation for com.xx.xx.db.xxDatabase. xxDatabase_Impl does not exist
解決方法:
def room_version = "2.3.0"
compile "android.arch.persistence.room:runtime:$room_version"
kapt "android.arch.persistence.room:compiler:$room_version"
//如果您已遷移到androidx
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
切記:一定要<u>在所有使用到room的module中</u>都添加一下“ kapt "androidx.room:room-compiler:$room_version"
3寺谤、遺留問題
1)關(guān)于外鍵注解的使用仑鸥??变屁?
2)@Embedded 注解的作用眼俊??粟关?疮胖?