資料
學習過程記錄
照着各種文章寫,簡單的實作都一樣,可跑起來總是挂
java.lang.RuntimeException: cannot find implementation for com.charliesong.roomtest.room.JavaDatabase. JavaDatabase_Impl does not exist
at androidx.room.Room.getGeneratedImplementation(Room.java:94)
at androidx.room.RoomDatabase$Builder.build(RoomDatabase.java:723)
就是我們那個database類裡擷取執行個體Room.databaseBuilder().build(),調用build方法的時候就挂了。
提示很明顯,這個database按道理系統應該自動生成一個Impl類的,可我們沒有自動生成,可也不知道咋讓他自動生成
public abstract class JavaDatabase extends RoomDatabase
各種解決都不行啊,不過好像和kotlin有關,最後我把room相關的類,都改成java寫,然後就可以了。
如下

image.png
後記
後來發現,room庫分kotlin版本和java版本的,引用的不同的東西,具體用啥,下邊有room的文檔位址可以查
kotlin寫的
apply plugin: 'kotlin-kapt'
//room
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
java寫的
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
整理下基本操作
最新的room版本可以到官網查詢,20190827目前都2.2了
room
第一步app的build.gradle下添加庫
def room_version = "1.1.1"
// or, for latest rc, use "1.1.1-rc1"
implementation "android.arch.persistence.room:runtime:$room_version"
// annotationProcessor "android.arch.persistence.room:compiler:$room_version"
kapt "android.arch.persistence.room:compiler:$room_version"
// optional - RxJava support for Room
implementation "android.arch.persistence.room:rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "android.arch.persistence.room:guava:$room_version"
// Test helpers
testImplementation "android.arch.persistence.room:testing:$room_version"
或者是androidx的
def room_version = "2.1.0-alpha02"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version" // Kotlin 的話用kapt
// 如果需要用到 rxjava
implementation "androidx.room:room-rxjava2:$room_version"
// 如果需要用到 guava
implementation "androidx.room:room-guava:$room_version"
// 需要用到相關測試工具的話
testImplementation "androidx.room:room-testing:$room_version"
如果用kotlin的話,最上邊應該是這樣的
apply plugin: 'com.android.application'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.charliesong.demo0327"
minSdkVersion 18
targetSdkVersion 27
versionCode 2
versionName "1.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
//指定room.schemaLocation生成的檔案路徑
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
添加實體類
當一個類用@Entity注解并且被@Database注解中的entities屬性所引用,Room就會在資料庫中為那個entity建立一張表。
預設Room會為entity中定義的每一個field都建立一個column。如果一個entity中有你不想持久化的field,那麼你可以使用@Ignore來注釋它們
另外記得entity必須有一個@PrimaryKey 主鍵字段
import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;
@Entity
public class Userjava {
@PrimaryKey(autoGenerate = true)
public int uid;
public String firstName;
public String lastName;
public int age;
@ColumnInfo(name = "region")//列的名字可以改
public String address;
}
添加資料通路對象dao
import android.arch.lifecycle.LiveData;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Update;
import java.util.List;
@Dao
public interface UserJavaDao {
@Insert
void insertAll(Userjava... userjava);
@Query("select * from Userjava")
LiveData> getUsersFromSync();
@Delete
int delete(Userjava userjava);//參數可以是數組,集合,傳回的是删除成功的條數
@Update
int update(Userjava... userjava);//參數可以是數組,集合,傳回的是update成功的條數
}
database類
版本号必須>=1
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.room.Database;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.arch.persistence.room.migration.Migration;
import android.support.annotation.NonNull;
import com.charliesong.demo0327.app.MyApplication;
//所有的entity注解的類,都得在這裡聲明
@Database(entities = {Userjava.class},version =4)
public abstract class JavaDatabase extends RoomDatabase {
//定義了幾個Dao注解的類,這裡就寫幾個抽象方法
public abstract UserJavaDao userJavaDao();
private static JavaDatabase javaDatabase;
public static JavaDatabase instance(){
if(javaDatabase==null){
synchronized (JavaDatabase.class){
if(javaDatabase==null){
javaDatabase= Room.databaseBuilder(MyApplication.myApplication,JavaDatabase.class,"test").
addCallback(new Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
System.out.println("onCreate==========="+db.getVersion()+"==="+db.getPath());
}
@Override
public void onOpen(@NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
System.out.println("onOpen==========="+db.getVersion()+"==="+db.getPath());
}
})
.allowMainThreadQueries()//允許在主線程查詢資料
.addMigrations(migration)//遷移資料庫使用,下面會單獨拿出來講
.fallbackToDestructiveMigration()//遷移資料庫如果發生錯誤,将會重新建立資料庫,而不是發生崩潰
.build();
}
}
}
return javaDatabase;
}
//資料庫更新用的
static Migration migration=new Migration(1,4) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
System.out.println("migrate============"+database.getVersion());
database.execSQL("ALTER TABLE Userjava "+ " ADD COLUMN address TEXT");
}
};
}
然後就可以測試拉。
使用livedata比較友善,隻處理資料庫的增删改,發生變化livedata自動會變化,我們弄個observer就ok了,清單會自動重新整理的
UtilRoomDB.getUserDao().usersFromSync.observe(this, Observer {
myAdapter.submitList(it)
})
我用的listAdapter
inner class MyAdapter(callback: DiffUtil.ItemCallback): ListAdapter(callback)
順道記錄些其他問題
Error:(22, 17) 警告: Schema export directory is not provided to the annotation processor so we cannot export the schema.
You can either provide `room.schemaLocation` annotation processor argument OR set exportSchema to false.
方法1
添加 exportSchema = false 就是不需要這個表的json資料
@Database(entities = { YourEntity.class }, version = 1, exportSchema = false)
public abstract class MovieDatabase extends RoomDatabase {
...
}
android {
compileSdkVersion 26
buildToolsVersion "26.0.2"
defaultConfig {
applicationId "com.xingen.architecturecomponents"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//指定room.schemaLocation生成的檔案路徑
javaCompileOptions {
annotationProcessorOptions {
arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
}
運作以後就能看到下邊的東西了,json檔案裡可以看到我們定義的表以及其包含的字段,主鍵等資訊

image.png
版本更新
列印下可以看到,第一次操作資料庫的時候,先create,再open
System.out.println("onCreate==========="+db.getVersion()+"==="+db.getPath());
onCreate===========0===/data/user/0/com.charliesong.demo0327/databases/test
onOpen===========1===/data/user/0/com.charliesong.demo0327/databases/test
更新操作
如果你改了版本,然後沒有寫Migration,那麼更新以後資料庫就被清空了。
這兩個方法,要麼别寫,要寫了你就得有對應的migration,
// .addMigrations(migration,migration2)//遷移資料庫使用,下面會單獨拿出來講
// .fallbackToDestructiveMigration()//遷移資料庫如果發生錯誤,将會重新建立資料庫,而不是發生崩潰
我們一般更新資料庫,應該是增加了字段或者修改了字段之類的。
//資料庫更新用的,從版本1更新到版本2
static Migration migration=new Migration(1,2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
System.out.println("migrate12============"+database.getVersion());
database.execSQL("ALTER TABLE Userjava "+ " ADD COLUMN country TEXT");
}
};
版本2更新到版本4,打算删除表中的一列
如果想改某列的類型,名稱啥的也一樣操作
.addMigrations(migration,migration2,migration3)
好像drop column不好使,是以就建立個臨時表,複制下資料然後删除老的,再把新表名字改回去
static Migration migration2=new Migration(2,3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
System.out.println("migrate23============"+database.getVersion());
database.execSQL("create table aaaa(uid int primary key,firstName text,lastName text,age text,address text)");
database.execSQL("insert into aaaa select uid ,firstName ,lastName ,age,address from Userjava");
database.execSQL("drop table Userjava");
database.execSQL("alter table aaaa rename to Userjava");
}
};
static Migration migration3=new Migration(3,4) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
System.out.println("migrate34============"+database.getVersion());
// database.execSQL("ALTER TABLE Userjava "+ " ADD COLUMN address TEXT");
}
};
日志如下,2個print都出來了,然後挂了
看了下,我們上邊建立那個aaaa的表,資料類型有點問題,那就改一下
Process: com.charliesong.demo0327, PID: 30844
java.lang.IllegalStateException: Migration didn't properly handle Userjava(com.charliesong.demo0327.room.Userjava).
Expected:
TableInfo{name='Userjava', columns={address=Column{name='address', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0}, lastName=Column{name='lastName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0}, firstName=Column{name='firstName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0}, uid=Column{name='uid', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1}, age=Column{name='age', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0}}, foreignKeys=[], indices=[]}
Found:
TableInfo{name='Userjava', columns={address=Column{name='address', type='text', affinity='2', notNull=false, primaryKeyPosition=0}, lastName=Column{name='lastName', type='text', affinity='2', notNull=false, primaryKeyPosition=0}, firstName=Column{name='firstName', type='text', affinity='2', notNull=false, primaryKeyPosition=0}, uid=Column{name='uid', type='int', affinity='3', notNull=false, primaryKeyPosition=1}, age=Column{name='age', type='text', affinity='2', notNull=false, primaryKeyPosition=0}}, foreignKeys=[], indices=[]}
at com.charliesong.demo0327.room.JavaDatabase_Impl$1.validateMigration(JavaDatabase_Impl.java:75)
at android.arch.persistence.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:87)
at android.arch.persistence.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onUpgrade(FrameworkSQLiteOpenHelper.java:133)
at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:256)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:163)
at android.arch.persistence.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:96)
at android.arch.persistence.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:54)
at android.arch.persistence.room.RoomDatabase.query(RoomDatabase.java:233)
at com.charliesong.demo0327.room.UserJavaDao_Impl$4.compute(UserJavaDao_Impl.java:165)
at com.charliesong.demo0327.room.UserJavaDao_Impl$4.compute(UserJavaDao_Impl.java:151)
at android.arch.lifecycle.ComputableLiveData$2.run(ComputableLiveData.java:100)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
這是成功的日志
11-15 09:15:27.546 com.charliesong.demo0327 I/System.out: migrate23============2
11-15 09:15:27.556 com.charliesong.demo0327 I/System.out: migrate34============2
11-15 09:15:27.606 com.charliesong.demo0327 I/System.out: onOpen===========4===/data/user/0/com.charliesong.demo0327/databases/test
new Migration(4,6) 裡邊2個版本号到底指的啥
為了測試,
@1 版本從4直接改到8,然後添加2個Migration
new Migration(4,6)和new Migration(4,8)
列印了下,發現隻走了4到8
@2 然後版本從8改成11,添加下邊2個
new Migration(8,10) new Migration(9,11)
然後2個都沒走,然後資料被清空了
看下Migration的查詢條件
存儲的時候是存在這個集合裡的
private SparseArrayCompat> mMigrations =
new SparseArrayCompat<>();
比如(4,6)和(4,8)那麼就是append一個key是4的,然後裡邊包含的key是6和8的sparsearray
簡單分析下,我們隻說更新,不說降級。
如下邏輯,從4更新到8,那麼根據start=4,找到一個sparsearray,這裡包含2個key是6和8,它是倒着來,找到第一個小于等于endVersion也就是8了,這個剛剛好就找一次。
然後分析下從8更新到11,有個(8,10)和(9,11)
先查8,找到一個end10,然後 從10開始查,結果10 沒找到,是以傳回一個null。因為傳回的null。再下邊那個方法可以看到,傳回null的話資料庫就被清空了。
private List findUpMigrationPath(List result, boolean upgrade,
int start, int end) {
final int searchDirection = upgrade ? -1 : 1;
while (upgrade ? start < end : start > end) {
SparseArrayCompat 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 onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
boolean migrated = false;
if (mConfiguration != null) {
List migrations = mConfiguration.migrationContainer.findMigrationPath(
oldVersion, newVersion);
if (migrations != null) {
for (Migration migration : migrations) {
migration.migrate(db);
}
mDelegate.validateMigration(db);
updateIdentity(db);
migrated = true;
}
}
if (!migrated) {//如果沒找到List migrations,那麼就會清空表的
if (mConfiguration != null && !mConfiguration.isMigrationRequiredFrom(oldVersion)) {
mDelegate.dropAllTables(db);
mDelegate.createAllTables(db);
} else {
throw new IllegalStateException("A migration from " + oldVersion + " to "
+ newVersion + " was required but not found. Please provide the "
+ "necessary Migration path via "
+ "RoomDatabase.Builder.addMigration(Migration ...) or allow for "
+ "destructive migrations via one of the "
+ "RoomDatabase.Builder.fallbackToDestructiveMigration* methods.");
}
}
}
添加一個新的entity
錯誤日志,說這個實體類沒有加到database,啥鬼啊,搞不懂。後來才查到,注解那裡要添加的,忘了
@Database(entities = {Userjava.class,HourseJava.class},version =1)
public abstract class JavaDatabase extends RoomDatabase
E:\androidStudio\RoomTest\app\src\main\java\com\charliesong\roomtest\room\HourseJavaDao.java:9:
錯誤: com.charliesong.roomtest.room.HourseJavaDao
is part of com.charliesong.roomtest.room.JavaDatabase
but this entity is not in the database. Maybe you forgot to add com.charliesong.roomtest.room.HourseJava
to the entities section of the @Database?
void insertHourse(HourseJava ...hourseJavas);
^
添加新的entity步驟
需要在database的注解字段entities裡添加,完事需要更新版本号,記得添加migration,
比如你從2更新到3,那麼必須添加一個Migration(2,3),否則如果沒找到這玩意,資料庫就被清空了。還得自己在migration方法裡添加create table的指令,建立新表,要不會報錯的。
後記
room這東西是編譯的時候生成相關的類的,databinding也是,有時候是room這邊有問題,導緻databinding生成失敗,結果你看錯誤資訊都是databinding的。
注解學習
錯誤記錄
Cannot figure out how to save this field into database. You can consider adding a type converter for it.
原因,實體類裡有個自定義對象,不認識
public DataType dataType;//這玩意就是個枚舉類型
解決辦法
@TypeConverters(DataTypeConverter.class)//注解用哪個類來解析存儲資料
public DataType dataType;
import androidx.room.TypeConverter;
//寫一個轉化類,其實就是需要兩個方法可以互相轉化這個自定義類,轉化為基本資料類型,也就是可以存入資料庫的
public class DataTypeConverter{
@TypeConverter
public OneTouchBean.DataType toDataType(int index){
return OneTouchBean.DataType.values()[index];
}
@TypeConverter
public int toIndex(OneTouchBean.DataType dataType){
return dataType.ordinal();
}
}
這個注解@TypeConverters可以寫在要解析的類上邊,也可以寫在@Database注解的類裡,可以參考workManager相關的包裡邊有,如下代碼
@Database(entities = {
Dependency.class,
WorkSpec.class,
WorkTag.class,
SystemIdInfo.class,
WorkName.class},
version = 6)
@TypeConverters(value = {Data.class, WorkTypeConverters.class})
public abstract class WorkDatabase extends RoomDatabase {
kotlin寫在屬性上,還是報錯,必須寫在類上邊
java.lang.IllegalStateException: getDatabase called recursively
代碼如下
在onCreate裡,資料庫建立成功以後,執行插入一些預設的資料操作,出錯了
public abstract class LocalDatabase extends RoomDatabase {
private static LocalDatabase database;
public static LocalDatabase instance() {
if (database == null) {
synchronized (LocalDatabase.class) {
if (database == null) {
database = Room.databaseBuilder(MainApplication.getCurrentInstance(), LocalDatabase.class, "test").
addCallback(new Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
System.out.println("onCreate===========" + db.getVersion() + "===" + db.getPath());
resetOneTouchData();//這裡的代碼挂了
}
//方法如下,就是插入資料,沒别的
private static void resetOneTouchData(){
database.oneTouchDao().insertAll();}
根源就在于db還沒關,我們又開了個db
簡單的修改,把這個插入資料的操作放到一個新的線程中,讓目前的db結束掉就好了。
Entity的實體類,有多個構造方法的時候會出錯
錯誤: Room cannot pick a constructor since multiple constructors are suitable. Try to annotate unwanted constructors with @Ignore.
按照提示注解掉多餘的,留下一個即可
注意
當初看demo也沒看到關閉資料庫,是以我也沒關,當然,關不關好像對資料沒啥影響
如果不關,那麼可以看到多了2個臨時檔案,隻有關閉的時候臨時檔案的資料才會寫入db裡,否則db檔案可能是空的,當然資料沒有丢失,你打開還在,不過是在臨時檔案裡而已。
public static void closeDb(){
if(javaDatabase!=null){
javaDatabase.close();
javaDatabase=null;
}
}

image.png
資料庫更新,表添加一列問題多多
需求:給表加一列如下的字段
private long placeUID =-1;
完事我就加了
public void migrate(@NonNull SupportSQLiteDatabase database) {
final String addColumn="alter table XXXBean add placeUID long default -1";
database.execSQL(addColumn);
}
結果挂了,異常提示expected 和found的不一樣
Expected:
placeUID = Column {
name = 'placeUID', type = 'INTEGER', affinity = '3', notNull = true, primaryKeyPosition = 0, defaultValue = 'null'
},
Found:
placeUID = Column {
name = 'placeUID', type = 'Long', affinity = '1', notNull = false, primaryKeyPosition = 0, defaultValue = '-1'
},
我實體類裡定義的是long,實際上room是當做int來處理的,是以,我隻好把sql語句改了,類型改成INTEGER 了
final String addColumn="alter table XXXBean add placeUID INTEGER default -1";
完事還是挂,再對比提示資訊,還有一個不一樣的notNull = true
繼續修改
final String addColumn="alter table XXXBean add placeUID INTEGER default -1";
終于完事了,不挂了
後記
沒事可以看看work這個庫,這裡也用到了room,看看人家咋存的

image.png
常用的簡單的增删改查
@Dao
public interface MyPlaceDao {
@Insert
void insertAll(MyPlaceBean... myPlaceBeans);
@Insert
long insertAll(MyPlaceBean myPlaceBeans);
@Query("select * from MyPlaceBean order by uid desc")
LiveData> getMyPlacesFromSync();
@Query("select * from myplacebean where locationData = :json")
MyPlaceBean getMyPlaceBeanByLocationJson(String json);
//COLLATE NOCASE 忽略大小寫
@Query("select * from myplacebean where changedName = :name COLLATE NOCASE and uid != :uid")
List getMyPlaceBeanByChangedName(String name,long uid);
@Delete
int delete(MyPlaceBean ...myPlaceBean);//參數可以是數組,集合,傳回的是删除成功的條數
@Delete
int delete(List myPlaceBean);//參數可以是數組,集合,傳回的是删除成功的條數
@Update
int update(MyPlaceBean... myPlaceBeans);//參數可以是數組,集合,傳回的是update成功的條數
@Update
int update(List myPlaceBeans);//參數可以是數組,集合,傳回的是update成功的條數
@Query("update myplacebean set changedName = :name where uid = :uid")
int updateName(String name, long uid);
}
需要注意下,這裡傳回的隻能是List,而不能是ArrayList,否則編譯就挂了
LiveData> getMyPlacesFromSync();
kotlin需要注意的地方
TypeConverters注解是寫在class上了,
下邊這個自增的主鍵要寫到構造方法裡,否則,它沒法自動生成id,預設就是個0
@PrimaryKey(autoGenerate = true)
var id:Int=0
@Entity
@TypeConverters(DataTypeConverter::class)
data class Task(
val name: String,
val deadline: String,
val priority: TaskPriority =TaskPriority.MEDIUM ,
var completed: Boolean = false,
@PrimaryKey(autoGenerate = true)
var id:Int=0
)
比如這樣寫,你會發現資料庫裡id都是0
data class Task(
val name: String,
val deadline: String,
val priority: TaskPriority =TaskPriority.MEDIUM ,
var completed: Boolean = false
){
@PrimaryKey(autoGenerate = true)
var id:Int=0
}