Room學(xué)習(xí)整理

目錄

[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ù)的方法
來源出自Android Develpers

上面是三者的關(guān)系,只有在Database中配置了的EntityDao才會被編譯進(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是對 EntityDao 管理的就好.

@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抽離出來單獨介紹.

RoomOrmDemo

參考

Room Persistence Library Part of Android Jetpack.

此處應(yīng)有簽名
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末命雀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子斩箫,更是在濱河造成了極大的恐慌吏砂,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乘客,死亡現(xiàn)場離奇詭異狐血,居然都是意外死亡,警方通過查閱死者的電腦和手機易核,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門匈织,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事缀匕∧删觯” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵乡小,是天一觀的道長阔加。 經(jīng)常有香客問我,道長满钟,這世上最難降的妖魔是什么胜榔? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮零远,結(jié)果婚禮上苗分,老公的妹妹穿的比我還像新娘。我一直安慰自己牵辣,他們只是感情好摔癣,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纬向,像睡著了一般择浊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逾条,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天琢岩,我揣著相機與錄音,去河邊找鬼师脂。 笑死担孔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吃警。 我是一名探鬼主播糕篇,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酌心!你這毒婦竟也來了拌消?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤安券,失蹤者是張志新(化名)和其女友劉穎墩崩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體侯勉,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡鹦筹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了址貌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盛龄。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出余舶,到底是詐尸還是另有隱情,我是刑警寧澤锹淌,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布匿值,位于F島的核電站,受9級特大地震影響赂摆,放射性物質(zhì)發(fā)生泄漏挟憔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一烟号、第九天 我趴在偏房一處隱蔽的房頂上張望绊谭。 院中可真熱鬧,春花似錦汪拥、人聲如沸达传。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宪赶。三九已至,卻和暖如春脯燃,著一層夾襖步出監(jiān)牢的瞬間搂妻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工辕棚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留欲主,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓逝嚎,卻偏偏與公主長得像扁瓢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子懈糯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容