目錄
[TOC]
這不是一篇很好的入門文章,僅做記錄使用,更好的還是直接參考文末的官方向?qū)?/p>
Room
Room 是Android提供的操作數(shù)據(jù)庫的高Api架構(gòu),避免了直接使用SQLite的一些重復(fù)代碼和麻煩,而且屬于Android Architecture Components,如果你還在用著SQLite可以嘗試使用Room.使用annotationProcessor來自動生成代碼.
- 代碼侵入很小,很容易從SQLite過度到Room.
- 提供數(shù)據(jù)庫升級測試,提供sql編譯時校驗;
- 靈活的返回對象,支持LiveData,RxJava.
Room 主要由三個部分
- Database : 數(shù)據(jù)庫持有者,使用@Database來標(biāo)識,用來連接Entity,DAO,配置數(shù)據(jù)庫的一些功能;
- Entity :用來映射表結(jié)構(gòu)的實體;
- DAO : 包含操作數(shù)據(jù)的方法
上面是三者的關(guān)系,只有在Database中配置了的Entity和Dao才會被編譯進(jìn)程識別,生成對應(yīng)的代碼
來看看配置完成后項目操作數(shù)據(jù)庫的所有代碼,這里我直接拿了官方代碼的例子
AppDatabase.java
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
UserDao.java
@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);
}
User.java
public class User {
@PrimaryKey
private int uid;
@ColumnInfo(name = "first_name")
private String firstName;
@ColumnInfo(name = "last_name")
private String lastName;
// Getters and setters are ignored for brevity,
// but they're required for Room to work.
}
沒有對比就沒有傷害,這是用Sqlte直接寫的,很難維護,Room一行,SQLite無數(shù)行,修改的時候要了人的老命
public int update(MicroSaidBean.DatasBean ad) {
if (ad.getPubtime() == null || (0 == ad.getPubtime())) {
return -1;
}
String[] updateFields = ad.getFields();
String[] updateValues = ad.getValues();
return sqliteUtil.update(TableUtil.MicroSaid.TABLE_NAME, updateFields,
updateValues, TableUtil.MicroSaid.MICROSAID_PUBTIME + "=?",
new String[]{ad.getPubtime().toString()});
}
String sql = "select * from " + TableUtil.MicroSaid.TABLE_NAME + " where " + TableUtil.MicroSaid.MICROSAID_PUBTIME + " = " + pubtime;
String[][] datas = sqliteUtil.executeSelectSql(sql, null);
MicroSaidBean.DatasBean datasBean = new MicroSaidBean.DatasBean();
if (!"".equals(datas[0][1])) {
datasBean.setPubtime(Long.valueOf(datas[0][1]));
}
if (!"".equals(datas[0][2])) {
datasBean.setContent(datas[0][2]);
}
當(dāng)然上面僅是一些基礎(chǔ)功能,大致介紹了Room的結(jié)構(gòu),下面正式開始Room的學(xué)習(xí).
首先添加依賴
def room_version = "1.1.1"
implementation "android.arch.persistence.room:runtime:$room_version"
annotationProcessor "android.arch.persistence.room:compiler:$room_version" // use kapt for Kotlin
Entity
和其它關(guān)系映射數(shù)據(jù)庫一樣,首先定義表結(jié)構(gòu),也就是Entity.制定表結(jié)構(gòu)
/**
* 書對應(yīng)的頁面信息
* tableName 表名,默認(rèn)類名
* foreignKeys 外鍵聲明,ForeignKey entity 外鍵對應(yīng)的實體, parentColumns 父實體對應(yīng)的 列名,一般為主鍵,childColumns 本實體對應(yīng)的列,
* 父類 onUpdate 更新的時候行為, onDelete 刪除時的行為
* inheritSuperIndices 是否允許從父類中繼承索引
* indices 設(shè)置索引 Index value="" 指定哪個列為索引
*/
@Entity(
foreignKeys = {@ForeignKey(entity = Book.class, parentColumns = "id", childColumns = "book_id", onUpdate = ForeignKey.NO_ACTION, onDelete = ForeignKey.NO_ACTION)},
inheritSuperIndices = false,
indices = {@Index(value = "book_id")}
)
public class Page {
//PrimaryKey 設(shè)置主鍵,是否自動增長
@PrimaryKey(autoGenerate = true)
private int id;//如果字段為私有的就需要有g(shù)et和set方法
//ColumnInfo
// name : 映射的實際列名,因為sqlLite不區(qū)分大小寫,所以使用_來區(qū)別單詞,否則顯示的就是bookid為表中的列名
// typeAffinity:實際類型 UNDEFINED 根據(jù)變量類型解析,還有其他類型可以指定,TEXT,INTEGER,REAL,BLOB
// index 是否建立索引,
// collate 指定sql查詢此字段的時候排序規(guī)則,UNSPECIFIED默認(rèn)為 BINARY大小寫敏感,NOCASE,RTRIM,LOCALIZED,UNICODE,
@ColumnInfo(name = "book_id", typeAffinity = UNDEFINED, index = false, collate = UNSPECIFIED)
private int bookId;
@ColumnInfo(name = "page_content")//
private String pageContent;
@Ignore//該字段不加入數(shù)據(jù)庫中
@ColumnInfo(name = "book_name")
public String bookName;
}
上面的代碼狈涮,主要介紹了,關(guān)于Entity的主要代碼鸭栖,以及所有的注解Api,上面對于表關(guān)系的描述歌馍,其實可以不加,但是一個好的表關(guān)系晕鹊,能夠更好的維護表的數(shù)據(jù);
補充一下onUpdate 和 onDelete 的參數(shù)
- NO_ACTION 什么也不做
- RESTRICT 約束模式,就拿上面的例子簡單來說,Book的主鍵 id 是 Page 的 book_id外鍵,那么他們倆是通過 id 和book_id進(jìn)行約束的,在Page的book_id外鍵存在的時候不允許刪除Book的id和修改Book的id,因為一旦修改成功,那么Page就亂掉了,無法形成對應(yīng)關(guān)系,就成了臟數(shù)據(jù)
- SET_DEFAULT,SET_NULL 兩個做的事情是一樣的,一個設(shè)置null一個設(shè)置默認(rèn)值,舉個列子,你現(xiàn)在刪除了Book id=2的數(shù)據(jù),那么Page 的 book_id=2 的這條數(shù)據(jù)的book_id 是給他設(shè)置 null 或者默認(rèn)值,因為之前的id沒了
- CASCADE 關(guān)聯(lián)默認(rèn),這個意識是,你刪了Book id=2 的數(shù)據(jù),對應(yīng)的Page book_id的數(shù)據(jù)也會被刪除,更新了id那么對應(yīng)的Page book_id字段也會更新
那么到這里Entity的所有內(nèi)容已經(jīng)說完了.
Dao
這里的Dao是定義數(shù)據(jù)處理的地方,就像Retrofit的retrofit.create(GitHubService.class);里的Services,用戶定義要查詢的語句,返回的內(nèi)容,然后返回用戶想要的數(shù)據(jù),Room幫用戶在編譯時生成對應(yīng)的代碼.
@Dao
public interface BookDao {
/**
* 插入數(shù)據(jù)
*
* @param book 要插入的數(shù)據(jù),必須是映射的實體或者映射實體的集合
* @return 返回插入了第幾行
*/
@Insert()
public long insert(Book book);
/**
* 增加多條數(shù)據(jù)
*
* @param books
*/
@Insert(onConflict = OnConflictStrategy.IGNORE)
public long[] insert(List<Book> books);
/**
* 刪除指定數(shù)據(jù)
*
* @param book 要刪除的對象
* @return 返回影響的行
*/
@Delete
public int delete(Book book);
/**
* 根據(jù)條件刪除指定數(shù)據(jù)
*
* @param bookId
* @return
*/
@Query("delete from book where id = :bookId")
public int deleteByUserId(int bookId);
@Query("delete from book")
public int deleteAll();
@Update
public void update(Book book);
@Update(onConflict = OnConflictStrategy.REPLACE)
public void update(List<Book> books);
@Query("select * from Book")
public List<Book> getAllBook();
/**
* 根據(jù)條件查詢
*
* @param bookName
* @return
*/
@Query("select * from book where id = :bookId")
public Book getBookByBookId(int bookId);
/**
* 根據(jù)條件查詢 返回多條數(shù)據(jù)
*
* @param bookName
* @return
*/
@Query("select * from book where book_name=:bookName")
public List<Book> getBooksByBookName(String bookName);
}
上面的例子顯示了基本的增刪改查,可以發(fā)現(xiàn)Room會根據(jù)返回值自動推測結(jié)果,例如刪除的時候你可以定義void,那么久沒有返回值,當(dāng)你改為int時,則會返回對應(yīng)的行數(shù).而且在寫Sql語句的時候as會提示你使用哪個字段,例如book,當(dāng)我打出b的時候回提示book,防止拼錯等低級操作,如果想動態(tài)填充數(shù)據(jù)使用:xxx
然后解釋一下 Insert,Update的用法.
有時插入數(shù)據(jù)和更新數(shù)據(jù)會產(chǎn)生沖突,所以就有了沖突之后要怎么解決,SQLite對于事務(wù)沖突定義了5個方案
OnConflictStrategy
- REPLACE,見名知意,替換,違反的記錄被刪除松却,以新記錄代替之
- ignore 違反的記錄保持原貌,其它記錄繼續(xù)執(zhí)行
- fail 終止命令溅话,違反之前執(zhí)行的操作得到保存
- abort 終止命令晓锻,恢復(fù)違反之前執(zhí)行的修改
- rollback 終止命令和事務(wù),回滾整個事務(wù)
事務(wù)解決由上到下越來越嚴(yán)謹(jǐn)
好了上面是基本操作,下面搞點高大上的,因為關(guān)系型數(shù)據(jù)庫避免不了的還有一對一,一對多,多對多的關(guān)系
一本書有很多頁,典型一對多.
表結(jié)構(gòu)
Book.java
@Entity//標(biāo)記實體為關(guān)系映射的實體,映射出數(shù)據(jù)
public class Book {
@Ignore//因為Room APT在生成代碼的時候需要構(gòu)造方法,因為這個類有兩個構(gòu)造方法,你要選擇其中一個忽略,否則有個錯不至死的警告
public Book(String bookName, double price, String author) {
this.bookName = bookName;
this.price = price;
this.author = author;
}
public Book() {
}
@PrimaryKey(autoGenerate = true)//PrimaryKey 設(shè)置主鍵,是否自動增長
public int id;
@ColumnInfo(name = "book_name")//如果不指定列名,默認(rèn)以bookName為列名
public String bookName;
public double price;
public String author;
}
Book 的id 和 Page 的 book_id 約束
@Dao
public interface PageDao {
@Insert
public long insert(Page page);
@Transaction //因為有兩個事務(wù)飞几,一個是 查書砚哆,一個是查書對應(yīng)的Page,把它們合成一個事務(wù),不加也沒事屑墨,只不過有警告
@Query("select * from book where id = :bookId")
public BookInfo getBookAndPageInfo(int bookId);
@Query("select * from Page")
public List<Page> getPageAll();
/**
* 聯(lián)表查詢詳細(xì)的數(shù)據(jù)
*
* @return
*/
@Query("select Page.id ,Page.book_id,Page.page_content,Book.book_name from Page " +
"inner join Book on Page.book_id = Book.id" +
" where Book.id = :bookId")
public List<PageDetail> getPageInfo(int bookId);
// public List<Page> getPageInfo(int bookId);最初 想用這個Page實體去接躁锁,但是最后發(fā)現(xiàn)book_name ignore之后,Room的代碼生成器卵史,就會跳過這個字段战转,導(dǎo)致無法映射這個字段
@Query("delete from Page where id = :pageId")
public int deletePageByInfo(int pageId);
}
PageDetail.java
public class PageDetail {
public int id;//如果字段為私有的就需要有g(shù)et和set方法
@ColumnInfo(name = "book_id")
public int bookId;
@ColumnInfo(name = "page_content")
public String pageContent;
@ColumnInfo(name = "book_name")
public String bookName;
}
數(shù)據(jù)映射
不僅實體可以映射出表結(jié)構(gòu),查出的數(shù)據(jù)也可以通過映射到實體
這里在 public List<PageDetail> getPageInfo(int bookId);中定義了一個返回的實體PageDetail,如果表中查詢出的字段和實體不匹配可以使用ColumnInfo來指定對應(yīng)的關(guān)系!
事務(wù)
引入事務(wù)的概念Transaction,有的查詢語句里面會有一個以上的操作,例如查完book再查page,那么被事務(wù)注解的方法,兩者屬于同一個事務(wù),只有事務(wù)中的兩個原子操作成功了,這次事務(wù)才是成功!
注意下面的BookInfo
@Query("select * from book where id = :bookId")
public BookInfo getBookAndPageInfo(int bookId);
BookInfo.java
public class BookInfo {
/**
* 這個方法叫嵌入,你可以想象這個include標(biāo)簽程腹,把字段分解到BookInfo實體里匣吊,然后查詢之后自動裝箱成Book實體
* public int id;
* public String bookName;
* public double price;
* public String author;
*/
@Embedded
public Book book;
/**
* 這個操作為了顯示標(biāo)注 表的關(guān)系
* entity 沒寫的情況下 默認(rèn)推測entity=返回值entity,也可以顯示指定,但必須是個映射實體,
* entityColumn 關(guān)聯(lián)的字段 parentColumn 父表的關(guān)聯(lián)字段寸潦,相當(dāng)于 page.book_id = book.id
* projection
*/
@Relation(parentColumn = "id", entityColumn = "book_id")
public List<Page> pages;
/**
* 這個就要顯示指定entity 因為返回值不是個映射實體色鸳,
* 而且 查出的是Page里的字段,所以PageSimple字段要 < Page 字段见转,
* projection 從查出的結(jié)果中抽出哪幾個字段,默認(rèn)從返回值實體的字段推測
* 這里@Relation(entity = Page.class, parentColumn = "id", entityColumn = "book_id", projection = {"id", "book_id"})
* = @Relation(entity = Page.class, parentColumn = "id", entityColumn = "book_id"})
*/
@Relation(entity = Page.class, parentColumn = "id", entityColumn = "book_id", projection = {"id", "book_id"})
public List<PageSimpleInfo> pageDetails;
}
上面有說過,Room會根據(jù)查詢的字段和返回值的字段進(jìn)行比對,然后生成對應(yīng)的實體,
- @Embedded: 嵌入,在select * from book where id = :bookId這句話查詢出的內(nèi)容應(yīng)該是 book表中的字段,但是用了Book對象來接,這里Room把字段裝箱成了Book
- @Relation 雖然查詢的是 book,而且sql沒有寫關(guān)聯(lián)兩個標(biāo)的操作,但是確把對應(yīng)的關(guān)系查出來了,這里Relation就起到了這個作用,仔細(xì)看方法的注釋.
如果上面的方法能夠理解,那我們繼續(xù)!
Database
Database起了一個連接作用,entities規(guī)定了哪些實體參與映射,version標(biāo)識數(shù)據(jù)庫版本,exportSchema是否導(dǎo)出數(shù)據(jù)庫結(jié)構(gòu)數(shù)據(jù),他是個抽象類, public abstract BookDao getBookDao();把我們剛剛寫好的Dao定義到里面,APT才會生成對應(yīng)的查詢方法
AppDatabase.java
/**
* entities 需要映射的類,如果不添加到這里,是不進(jìn)行映射的
* version 數(shù)據(jù)庫版本
* exportSchema 是否導(dǎo)出表結(jié)構(gòu),默認(rèn)為true,建議不修改,因為通過導(dǎo)出的文件可以看到數(shù)據(jù)庫更新的歷史記錄
*/
@Database(entities = {Book.class, Page.class, User.class, Trade.class}, version = 3, exportSchema = true)
@TypeConverters({DateConverter.class})
public abstract class AppDatabase extends RoomDatabase {
private static volatile AppDatabase INSTANCE;
public static String DATABSE_NAME = "book_database";
//***************************定義操作的Dao類,必要,其它的愛寫哪寫哪***********************
/**
* 定義 訪問數(shù)據(jù)庫的類
*
* @return
*/
public abstract BookDao getBookDao();
public abstract PageDao getPageDao();
public abstract UserDao getUserDao();
public abstract TradeDao getTradeDao();
//****************************************************************
/**
* 獲取數(shù)據(jù)庫實例,配置數(shù)據(jù)參數(shù),
*數(shù)據(jù)庫名稱,數(shù)據(jù)庫是否允許在主進(jìn)程中,升級配置.升級回調(diào),打開數(shù)據(jù)回調(diào)
*
* @param context ctx
* @return AppDatabase
*/
public static AppDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, DATABSE_NAME)
.addMigrations(MIGRATION1_2)
.build();
}
}
}
return INSTANCE;
}
private static final String TAG = "hb";
/**
* 增加了user表
*/
public static Migration MIGRATION1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
Log.d(TAG, "migrate: " + database.getVersion());
database.execSQL("CREATE TABLE IF NOT EXISTS User (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT)");
}
};
public static Migration MIGRATION2_3 = new Migration(2, 3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
Log.d(TAG, "migrate: " + database.getVersion());
database.execSQL("CREATE TABLE IF NOT EXISTS Trade (`id` TEXT NOT NULL, `user_id` INTEGER NOT NULL, `book_id` INTEGER NOT NULL, `trade_time` INTEGER, `trade_price` REAL NOT NULL, PRIMARY KEY(`id`))");
database.execSQL("alter table User add column birthday INTEGER");
}
};
}
仔細(xì)看完上面的代碼.只要能得出Database是對 Entity 和 Dao 管理的就好.
@TypeConverters這個方法寫在Database那就是全局應(yīng)用,寫在Entity就只應(yīng)用在本表中.也是根據(jù)返回值和參數(shù)來規(guī)定什么時候使用.
public class DateConverter {
@TypeConverter
public Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}
@TypeConverter
public Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
Trade.java
@Entity
@TypeConverters({DateConverter.class})
public class Trade {
public Trade(@NonNull String id, int userId, int bookId, Date tradeTime, double tradePrice) {
this.id = id;
this.userId = userId;
this.bookId = bookId;
this.tradeTime = tradeTime;
this.tradePrice = tradePrice;
}
@PrimaryKey
@NonNull
public String id;
@ColumnInfo(name = "user_id")
public int userId;
@ColumnInfo(name = "book_id")
public int bookId;
@ColumnInfo(name = "trade_time")
public Date tradeTime;
@ColumnInfo(name = "trade_price")
public double tradePrice;
}
數(shù)據(jù)庫升級
剛才在Database看到了這么一行代碼:
public static Migration MIGRATION1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
Log.d(TAG, "migrate: " + database.getVersion());
database.execSQL("CREATE TABLE IF NOT EXISTS User (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT)");
}
};
new Migration(1, 2)意思是版本1~2到的升級,當(dāng)數(shù)據(jù)庫版本1到2的時候就會回調(diào)這個方法,上面的代碼就是添加User表.
其實我們在以往其它SQLite架構(gòu)里面,升級數(shù)據(jù)的時候不需要主動添加表,頂多改下版本號就完了,例如GreenDao.但是Room不行,他會在表中增加哈希碼.增加表必須要有更新的操作!
如果你想跨版本new Migration(1, 3),那就是舊版本1,新代碼的版本是3那么就回調(diào)里面的方法,而2~3不會調(diào)用這個方法!
測試
Room支持?jǐn)?shù)據(jù)庫升級測試,在以往的開發(fā)過程中,由于增加了字段而沒有執(zhí)行對應(yīng)的sql,導(dǎo)致調(diào)用數(shù)據(jù)庫的崩潰,Room提供了版本更新時的測試,原理是根據(jù)每個版本的表結(jié)構(gòu)生成對應(yīng)的文件,當(dāng)測試的時候會把以往的版本信息讀取出來,然后結(jié)合要升級的版本結(jié)構(gòu)去驗證是否成功!有了這個數(shù)據(jù),無論從什么版本升級都有據(jù)可查!
配置gradle
//添加依賴
def room_version = "1.1.1"
// Test helpers
androidTestImplementation "android.arch.persistence.room:testing:$room_version"
//配置表結(jié)構(gòu)文件要輸出的文件夾
android {
defaultConfig {
//.....
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
}
編譯一下就會在app->schemas->包名下生成對應(yīng)的表關(guān)系文件
開始測試
因為要操作數(shù)據(jù)庫,所以要在androidTest中使用
MigrationTestHelper migrationTestHelper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(), AppDatabase.class.getCanonicalName(), new FrameworkSQLiteOpenHelperFactory());
migrationTestHelper.createDatabase(AppDatabase.DATABSE_NAME, 1);//創(chuàng)建版本庫1的版本,必要條件
migrationTestHelper.runMigrationsAndValidate(AppDatabase.DATABSE_NAME, 3, false, MIGRATION1_2, MIGRATION2_3);
InstrumentationRegistry用來模擬環(huán)境可以從中獲取上下文對象等.
MigrationTestHelper(Instrumentation instrumentation ,String assetsFolder ,SupportSQLiteOpenHelper.Factory openFactory ),這里注意第二個參數(shù)assetsFolder,就是那些自動生成的app->schemas文件夾,傳Database路徑就好
-
migrationTestHelper.createDatabase(AppDatabase.DATABSE_NAME, 1);
- 第一個參數(shù)數(shù)據(jù)庫名稱,
- 第二個參數(shù)版本號,這個方法是為了創(chuàng)建對應(yīng)的版本數(shù)據(jù)的
-
migrationTestHelper.runMigrationsAndValidate(AppDatabase.DATABSE_NAME, 3, false, MIGRATION1_2, MIGRATION2_3);
- 參數(shù)一: 數(shù)據(jù)庫名,
- 參數(shù)二: 要升級到哪個版本的數(shù)據(jù)庫,
- 參數(shù)三: 出問題是否刪除表,true,刪除表;
- 參數(shù)四: 升級的回調(diào)實現(xiàn)
查詢模式
Room數(shù)據(jù)庫不允許在UI線程執(zhí)行任何數(shù)據(jù)庫相關(guān)的操作,雖然可以通過設(shè)置 .allowMainThreadQueries()必過校驗但是不建議,;那么怎么解決插入在異步線程,查詢也在異步線程的同步問題呢?Room數(shù)據(jù)庫提供了LiveData
LiveData
@Query("select Page.id ,Page.book_id,Page.page_content,Book.book_name from Page " +
"inner join Book on Page.book_id = Book.id")
public LiveData<List<PageDetail>> getAllPageInfo();
這里我們需要添加
def room_version = "1.1.1"
implementation "android.arch.lifecycle:extensions:$room_version"http://聲明周期
然后就可以使用AndroidViewModel
public class PageViewModel extends AndroidViewModel {
public PageViewModel(@NonNull Application application) {
super(application);
AppDatabase database = AppDatabase.getDatabase(application);
pageDao = database.getPageDao();
prepare(database);
allPageInfo = pageDao.getAllPageInfo();
}
public void prepare(AppDatabase database) {
BookDao bookDao = database.getBookDao();
Book newBook = new Book("一本小說", 10.0, "小FaFa");
new AsyAddBook(bookDao, new IBookCallBack() {//默認(rèn)情況下,Room的數(shù)據(jù)庫操作都要在子線程,但是可以在AppDatabase里修改
@Override
public void bookId(int id) {
bookId = id;
}
}).execute(newBook);
}
public LiveData<List<PageDetail>> getAllPageInfo() {
return allPageInfo;
}
static class AsyInsertPage extends AsyncTask<Integer, Void, Void> {
@Override
protected Void doInBackground(Integer... bookIds) {
pageDao.insert(getPage(bookIds[0]));
Thread.sleep(1000);//模擬耗時操作
pageDao.insert(getPage(bookIds[0]));
Thread.sleep(1000);//模擬耗時操作
pageDao.insert(getPage(bookIds[0]));
}
}
static class AsyAddBook extends AsyncTask<Book, Void, Integer> {
@Override
protected Integer doInBackground(Book... books) {
bookDao.deleteAll();
bookDao.insert(books[0]);
Book book = bookDao.getBookByBookName("一本小說");
if (bookCallBack != null) {
bookCallBack.bookId(book.id);
}
return book.id;
}
}
}
MainActivity.java
pageViewModel = ViewModelProviders.of(this).get(PageViewModel.class);
LiveData<List<PageDetail>> allPageInfo = pageViewModel.getAllPageInfo();
allPageInfo.observe(this, new Observer<List<PageDetail>>() {
@Override
public void onChanged(@Nullable List<PageDetail> pageDetails) {
StringBuffer sb = new StringBuffer();
for (PageDetail pageDetail : pageDetails) {
sb.append(pageDetail.toString()).append("\n");
}
tvContent.setText(sb.toString());
}
});
當(dāng)查詢的內(nèi)容發(fā)生變化的時候自動會回調(diào)onChanged方法
詳情可以看代碼源碼地址
RxJava
添加依賴
def room_version = "1.1.1"
implementation "android.arch.persistence.room:rxjava2:$room_version" //rxjava返回值
和其它沒什么區(qū)別,就是包裹一下返回值
@Query("select Page.id ,Page.book_id,Page.page_content,Book.book_name from Page " +
"inner join Book on Page.book_id = Book.id")
public Flowable<List<PageDetail>> getAllPageInfoRx();
獲取數(shù)據(jù)
subscribe = pageDao.getAllPageInfoRx().subscribeOn(Schedulers.io()).subscribe(new Consumer<List<PageDetail>>() {
public static final String TAG = "hb";
@Override
public void accept(List<PageDetail> pageDetails) throws Exception {
Log.d(TAG, "accept: " + pageDetails.toString());
}
});
需要注意的地方
- 在測試升級庫的時候依賴的主要是表的json文件,一定要先改數(shù)據(jù)庫版本再修改表的實體,不然會出現(xiàn)和手機上的數(shù)據(jù)庫版本不一致的問題,如果發(fā)現(xiàn)有問題,把代碼改回來,然后重新編譯一下就和原來的一樣了.
- 編譯不通過,NotFoundClass CodeC,這里是因為org.apache.http.legacy HttpClent包沖突導(dǎo)致的,把libs下的HttpClent去掉,build.gradle 添加
android{
useLibrary 'org.apache.http.legacy'
}
代碼
因為先寫的代碼再寫的文章,就顯得邏輯沒那么通暢,沒有按照先簡單后容易的方式去寫,而是直接把遇到的一下說完,希望有的地方能幫助到你,已經(jīng)同步代碼放到了GitHub上;又因為代碼有注釋,所以沒有將Api抽離出來單獨介紹.
參考
Room Persistence Library Part of Android Jetpack.