Markdown版本筆記 | 個人GitHub首頁 | 個人博客 | 個人微信 | 個人郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
Room是什麼?java
Room是一個持久性數據庫。react
The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.android
Room持久性數據庫提供了SQLite的抽象層,以便在充分利用SQLite的同時容許流暢的數據庫訪問。git
Room 的優勢
在Android領域,目前已有很多優秀的開源的數據庫給你們使用,如SQLite、XUtils、greenDao、Realm,那爲何咱們還要去學習使用這個庫呢?由於Room有下面幾個優勢:github
編譯時
就會驗證 - 在編譯時檢查每一個@Query和@Entity等,而且它不只檢查語法問題,還會檢查是否有該表,這就意味着沒有任何運行時錯誤的風險可能會致使應用程序崩潰LiveData
、rxjava
集成,特別是與LiveData
等架構組件配合使用後,能產生讓人驚歎的效果Room 的組成
Room中有三個主要組件組成:sql
數據庫
。註解定義了一系列entities,而且類中提供一系列Dao的抽象方法,也是下層主要鏈接的訪問點。註解的類應該是一個繼承 RoomDatabase 的抽象類。在運行時,你能經過調用Room.databaseBuilder()
或者 Room.inMemoryDatabaseBuilder()
得到一個實例建立表
,Database類中的entities數組經過引用這些entity類建立數據庫表。每一個entity中的字段都會被持久化到數據庫中,除非用@Ignore
註解@Database
的類必須包含一個沒有參數的且返回註解爲@Dao
的類的抽象方法。在編譯時,Room建立一個這個類的實現。當一個類被註解爲@Entity
而且引用到帶有@Database
註解的entities
屬性,Room爲這個數據庫引用的entity建立一個數據表。 數據庫
Entity類可以有一個空的構造函數(若是dao類可以訪問每一個持久化的字段)或者一個參數帶有匹配entity中的字段的類型和名稱的構造函數json
Entity的字段必須爲public或提供setter或者getter方法。數組
Ignore:忽略
默認狀況下,Room爲每一個定義在entity中的字段建立一個列。若是一個entity的一些字段不想持久化,可使用@Ignore
註解它們
@Ignore Bitmap picture;
PrimaryKey:主鍵
每一個entity必須定義至少一個字段做爲主鍵,即便這裏只有一個字段,仍然須要使用@PrimaryKey
註解這個字段。而且,若是想Room動態給entity分配自增主鍵,能夠設置@PrimaryKey的autoGenerate
屬性爲true。若是entity有個組合的主鍵,你可使用@Entity
註解的primaryKeys屬性:
@PrimaryKey(autoGenerate = true) public int id // 自增主鍵
組合主鍵:
@Entity(primaryKeys = {"firstName", "lastName"}) class User {}
tableName:數據庫的表名
默認狀況下,Room使用類名做爲數據庫的表名。若是但願表有一個不一樣的名稱,設置@Entity註解的tableName屬性,以下所示:
@Entity(tableName = "users") class User {}
注意: SQLite中的表名是大小寫敏感的。
列名稱:ColumnInfo
Room使用字段名稱做爲列名稱。若是你但願一個列有不一樣的名稱,爲字段增長@ColumnInfo
註解
@ColumnInfo(name = "first_name") public String firstName;
indices:索引
數據庫索引能夠加速數據庫查詢,@Entity的indices
屬性能夠用於添加索引。在索引或者組合索引中列出你但願包含的列的名稱:
@Entity(indices = {@Index("name"), @Index({"first_name", "last_name"}),}) public class User {}
有時,表中的某個字段或字段組合須要確保惟一性,能夠設置@Entity的@Index註解的unique屬性爲true。
@Entity(indices = {@Index(value = {"first_name", "last_name"}, unique = true)}) public class User {}
foreignKeys:外鍵約束
例如:若是有一個entity叫book,你能夠經過使用@ForeignKey註解定義它和user的關係:
@Entity(foreignKeys = @ForeignKey(entity = User.class, parentColumns = "id", childColumns = "user_id")) class Book {}
外鍵是十分強大的,由於它們容許你指明當引用的entity被更新後作什麼。
例如,若是相應的user實例被刪除了,你能夠經過包含@ForeignKey註解的onDelete=CASCADE
屬性讓SQLite爲這個user刪除全部的書籍。SQLite處理@Insert(OnConflict=REPLACE)做爲一個REMOVE和REPLACE操做而不是單獨的UPDATE操做。這個替換衝突值的方法可以影響你的外鍵約束。
Embedded:嵌入式字段
有時,但願entity中包含一個具備多個字段的對象
做爲字段。在這種狀況下,可使用@Embedded
註解去表明一個但願分解成一個表中的次級字段的對象。接着你就能夠像其餘單獨的字段那樣查詢嵌入字段:
@Embedded public Address address;
class Address { @ColumnInfo(name = "post_code") public int postCode; //嵌入式字段還能夠包含其餘嵌入式字段 }
若是一個實體具備相同類型的多個內嵌字段,則能夠經過設置前綴屬性(prefix)使每一個列保持唯一。而後將所提供的值添加到嵌入對象中每一個列名的開頭
@Embedded(prefix = "foo_") Coordinates coordinates;
對象引用
SQLite是個關係型數據庫,可以指明兩個對象的關係。大多數ORM庫支持entity對象引用其餘的。Room明確的禁止這樣。更多細節請參考Understand why Room doesn’t allow object references
Room的三大組件之一Dao,以一種乾淨的方式去訪問數據庫。
Room 不容許在主線程中訪問數據庫。除非在建造器中調用allowMainThreadQueries()
,這可能會形成長時間的鎖住UI。
注意,異步查詢API(返回LiveData或者RxJava流的查詢)從這個規則中豁免,由於它們異步的在後臺線程中進行查詢。
在Dao中建立一個方法而且使用@Insert
註解它,Room會爲其生成一個實現,此實現會在單獨事務中插入全部參數到數據庫中:
@Insert long insertUser(User user); //參數至少有一個,有一個時能夠返回long(表明新插入item的rowId)或不返回 @Insert long[] insertAll(User... users); //參數也能夠是集合或者數組,此時能夠返回long[]或者List<Long>或不返回 @Insert(onConflict = OnConflictStrategy.REPLACE) void insertAll(List<User> users);//能夠執行事務操做
long id = db.userDao().insertUser(new User("哎")); List<Long> ids = db.userDao().insertAll(new User("哎哎" + new Random().nextInt(100))); db.userDao().insertAll(Arrays.asList(new User("哎啊"), new User("啊哎")));
@Insert方法必須至少有一個參數,若是參數只有一個,它能夠返回一個新插入item的rowId的long值(或不返回)
若是參數是一個集合或數組,它能夠返回long[]
或者List<Long>
(或不返回)
@Insert,@Update均可以執行事務操做,定義在OnConflictStrategy
註解類中:
@Retention(SOURCE) public @interface OnConflictStrategy { int REPLACE = 1; //替換舊數據 int ROLLBACK = 2; //回滾事務 int ABORT = 3; //就退出事務 int FAIL = 4; //使事務失敗 int IGNORE = 5; //忽略衝突 }
@Delete是一個從數據庫中刪除一系列給定參數的entities的慣例方法。它使用主鍵
找到要刪除的entities:
@Delete int delete(User user);//根據主鍵刪除entities,能夠沒有返回值或者返回int,返回int值時表示刪除的數量 @Delete int deleteAll(List<User> users); //參數至少有一個,也能夠是集合或者數組,參數必須是使用@Entity註解標註的類,且不能爲null
User user = db.userDao().findUserByName("啊哎"); //若是沒找到的話返回null if (user != null) Log.i("bqt", "【delete數量】" + db.userDao().delete(user)); //不能傳入空,不然崩潰 Log.i("bqt", "【deleteAll數量】" + db.userDao().deleteAll(db.userDao().getAll()));
儘管一般不是必須的,你可以擁有這個方法返回int值指示數據庫中刪除的數量。
沒有提供根據主鍵等方式刪除(包括更新)數據的方式,參數必須是使用@Entity註解標註的類,不能是字符串!
@Update
是更新一系列entities集合、給定參數的慣例方法。它使用query來匹配每一個entity的主鍵:
@Update int updateAll(User... users);//規則和delete同樣
儘管一般不是必須的,你可以擁有這個方法返回int值指示數據庫中更新的數量。
@Query 是DAO類中使用的主要註解,每一個@Query方法在編譯時
(而不是運行時)被校驗,若是查詢包含語法錯誤,或者若是用戶表不存在
,Room會報出合適的錯誤消息:
@Query("SELECT * FROM table_user") List<User> getAll();//返回值爲集合或數組的話,返回找到的全部數據,沒找到時返回長度爲0的集合或數組
查詢時傳入參數
若是你須要傳入參數到查詢語句中去過濾操做,你可使用方法參數
:
@Query("SELECT * FROM user WHERE age > :minAge") User[] loadAllUsersOlderThan(int minAge); @Query("SELECT * FROM user WHERE uid IN (:userIds)") List<User> loadAllByIds(int[] userIds);
當這個查詢在編譯期被處理,Room會匹配:minAge
綁定的方法參數。Room經過使用參數名稱
執行匹配,若是沒有匹配到,在你的app編譯期將會報錯。
查詢時能夠傳入多個參數或者屢次引用同一個參數。
@Query("SELECT * FROM user WHERE first_name LIKE :first AND last_name LIKE :last LIMIT 1") User findByName(String first, String last);
返回列中的子集
多數時候,咱們僅須要獲取一個entity中的部分字段。例如,你的UI可能只展現user第一個和最後一個名稱,而不是全部關於用戶的細節。經過獲取展現在UI的有效數據列可使查詢完成的更快
。
只要查詢中列結果集可以被映射到返回的對象中,Room容許你返回任何java對象
。例如,經過建立以下POJO拿取用戶的姓和名:
@Query("SELECT first_name, last_name FROM user") List<NameTuple> loadFullName();
public class NameTuple { @ColumnInfo(name="first_name") public String firstName; @ColumnInfo(name="last_name") public String lastName; }
Room理解查詢返回first_name和last_name的列值被映射到NameTuple類中。所以,Room可以生成合適的代碼。若是查詢返回太多columns,或者一個列不存在,Room將會報警。
返回LiveData對象
使用返回值類型爲LiveData實現數據庫更新時ui數據自動更新:
db.userDao().loadUsersByName2("%哎%") //能夠在主線程調用 .observe(this, users -> tvTitle.setText("【最新查詢結果】" + new Gson().toJson(users)));
返回RxJava對象
Room也能返回RxJava2的Publisher和Flowable對象
,需添加android.arch.persistence.room:rxjava2
依賴:
@Query("SELECT * from user where uid = :uid") Flowable<User> findUserById(int uid);
直接遊標訪問
若是你的應用邏輯直接訪問返回的行,你能夠從你的查詢當中返回一個Cursor對象:
@Query("SELECT * FROM user WHERE uid > :uid LIMIT 5") Cursor biggerId(int uid);//不推薦
注意:很是不建議使用Cursor API 由於它不能保證行是否存在或者行包含什麼值。
使用這個功能僅僅是由於你已經有一個返回cursor的代碼,而且你不能輕易的重構。
多表查詢
在SQLite數據庫中,咱們能夠指定對象之間的關係,所以咱們能夠將一個或多個對象與一個或多個其餘對象綁定。這就是所謂的一對多和多對多的關係。
一些查詢可能訪問多個表去查詢結果,Room容許你寫任何查詢,因此你也能鏈接表。
以下代碼段展現如何執行一個根據借書人姓名
模糊查詢借的書
的相關信息。
@Query("SELECT * FROM book " + "INNER JOIN loan ON loan.book_id = book.id " + "INNER JOIN user ON user.id = loan.user_id " + "WHERE user.name LIKE :userName") List<Book> findBooksBorrowedByNameSync(String userName);
從這些查詢當中也能返回POJOs,例如,能夠寫一個POJO去裝載userName和petName,以下:
@Query("SELECT user.name AS userName, pet.name AS petName FROM user, pet WHERE user.id = pet.user_id") LiveData<List<UserPet>> loadUserAndPetNames();
class UserPet { public String userName; public String petName; }
Room爲原始類型
和可選的裝箱類型
提供嵌入支持。然而,有時你可能使用一個單獨存入數據庫的自定義數據類型
。爲了添加這種類型的支持,你能夠提供一個TypeConverter
把自定義類轉化爲一個Room可以持久化的已知類型。
例如:若是咱們想持久化Date的實例,咱們能夠寫以下TypeConverter在數據庫中去存儲相等的Unix時間戳:
public Date birthday;
public class Converters { @TypeConverter public static Date fromTimestamp(Long value) { return value == null ? null : new Date(value); } @TypeConverter public static Long dateToTimestamp(Date date) { return date == null ? null : date.getTime(); } }
接着,你增長@TypeConverters
註解到RoomDatabase
子類:
@Database(entities = {User.java}, version = 1) @TypeConverters({Converter.class}) public abstract class AppDatabase extends RoomDatabase { public abstract UserDao userDao(); }
使用這些轉換器後,你就能夠像使用的原始類型同樣使用自定義類型了:
@Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to") List<User> findUsersBornBetweenDates(Date from, Date to);
注意:除了將 @TypeConverters 放在RoomDatabase裏面影響全局外,還能夠將 @TypeConverter 限制在不一樣的範圍內,包含單獨的entity、Dao和Dao中的 methods,具體查看官方文檔。
一般,咱們使用SQL語句模糊查詢時是這樣的:
select * from user where name like '%包_天%'; //%表明任意個字符,_表明一個字符
可是在Dao中不能簡單地這麼寫,任你各類拼接啊、轉義啊,都不能把那個%
給它弄進去。
這裏不得不說一個Room的一個好處,就是你在寫sql語句的時候它會檢查,格式不對,裏面會報紅線,根本就編譯不經過。
皇天不負有心人,最終在Stack Overflow上面搜了一下,找到了最終答案,應該是這樣的
@Query("SELECT * from user where last_name LIKE '%' || :name || '%'") LiveData<List<User>> loadUsersByName(String name);
原來是用雙豎槓去拼接,而不是加號,欲哭無淚啊。而後我就去搜了一下sql語句拼接,找到這樣一段話:
在SQL中的SELECT語句中,可以使用一個特殊的操做符來拼接兩個列。根據你所使用的DBMS,此操做符可用加號(+)或兩個豎槓(||)表示。
在MySQL和MariaDB中,必須使用特殊的函數。
Access和SQL Server使用 + 號。
SQLite、DB二、Oracle、PostgreSQL、Open Office Base使用 ||。
其實除了我上面用雙豎槓拼接的方式外,還有一種方法,就是在傳參的時候,把%_%
給拼接好,這種方式我也是測試了一下,也是能夠的,由於最終都是轉換成sql的標準查詢語句。
db.userDao().loadUsersByName("%包_天%")
並且,我感受這種方式寫起來更優雅,應該也是Google變相推薦的方式。
總結
db.userDao().loadUsersByName("%包_天%")
方法參數
先後使用||
拼接,如:@Query("SELECT * from user where name LIKE '_%' || :name || '%'")
在編譯時,將數據庫的模式信息導出到JSON文件中,這樣可有利於咱們更好的調試和排錯(DataBase的exportSchema = true)
module中的build.gradle:
android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]//room的數據庫概要、記錄 } } } }
配置完畢以後,咱們在構建項目以後會在對應路徑生成schemas文件夾。
文件夾內的1.json
等文件分別是數據庫版本一、二、3等版本對應的概要、記錄。
若是我們刪除了entity中的一個字段,運行程序後,就會出現下面這個問題。
java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
大體的意思是:你修改了數據庫,可是沒有升級數據庫的版本
這時候我們根據錯誤提示增長版本號,但沒有提供migration,APP同樣會crash。
java.lang.IllegalStateException: A migration from 1 to 2 was required but not found. Please provide the necessary Migration path via
RoomDatabase.Builder.addMigration(Migration ...)
or allow for destructive migrations via one of theRoomDatabase.Builder.fallbackToDestructiveMigration*
methods.
大體的意思是:讓咱們添加一個addMigration或者調用fallbackToDestructiveMigration完成遷移
接下來,我們增長版本號並使用fallbackToDestructiveMigration()
,雖然可使用了,可是咱們會發現,數據庫的內容都被咱們清空了,顯然這種方式是不友好的。若是不想清空數據庫,就須要提供一個實現了的migration。
Room容許使用
Migration
類保留用戶數據。每一個Migration類在運行時指明一個開始版本和一個結束版本,Room執行每一個Migration類的migrate()
方法,使用正確的順序去遷移數據庫到一個最近版本。
若是不提供必需的Migrations類,Room會重建數據庫,這意味着你將丟失數據庫中的全部數據。
好比我們要在Department中添加phoneNum
public class Department { @PrimaryKey(autoGenerate = true) int id; String dept; @ColumnInfo(name = "emp_id") int empId; @ColumnInfo(name = "phone_num") String phoneNum; }
一、把版本號自增
@Database(entities = {Department.class, Company.class}, version = 2, exportSchema = false) @TypeConverters(DateConverter.class) public abstract class DepartmentDatabase extends RoomDatabase {}
二、添加一個version:1->2的migration
static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL("ALTER TABLE department ADD COLUMN phone_num TEXT"); } };
三、把migration 添加到 databaseBuilder
Room.databaseBuilder(context, DepartmentDatabase.class, DB_NAME) .addMigrations(MIGRATION_1_2) .build();
再次運行APP就會發現,數據庫表更新了,而且舊數據也保留了。
在平時的開發時,數據庫的升級並不老是循序漸進的從 version: 1->2,2->3,3->4。老是會出現 version:1->3,或 2->4 的狀況。這時候咱們又該怎麼辦呢?
方法很簡單。當用戶升級 APP 時,咱們替用戶升級數據庫版本。
具體作法:
version:1->2
database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))"); //建立表
version:2->3
database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER"); //添加字段
version:3->4
//字段類型修改 db.execSQL("CREATE TABLE db_new (_id TEXT, _name TEXT, _phone INTEGER, PRIMARY KEY(_id))"); //建立表 db.execSQL("INSERT INTO db_new (_id, _name, _phone) SELECT _id, _name, _phone FROM db_old"); //複製表 db.execSQL("DROP TABLE db_old"); //刪除表 db.execSQL("ALTER TABLE db_new RENAME TO students"); //修改表名稱
而後把 migration 添加到 Room database builder:
Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name") .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4) .build();
這樣,要是用戶剛下載的 APP,目前咱們定義了migrations:version 1 到 2, version 2 到 3, version 3 到 4, 因此 Room 會一個接一個的觸發全部 migration。
其實 Room 能夠處理大於 1 的版本增量:咱們能夠一次性定義一個從 1 到 4
的 migration,以提高遷移的速度。
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_1_4)
implementation "android.arch.persistence.room:runtime:1.1.1" //Room annotationProcessor "android.arch.persistence.room:compiler:1.1.1" testImplementation "android.arch.persistence.room:testing:1.1.1" //Test helpers for Room implementation "android.arch.persistence.room:rxjava2:1.1.1" //爲room添加rxjava支持庫 implementation "android.arch.lifecycle:reactivestreams:1.1.1" //和LiveData一塊兒使用
@Database(entities = {User.class}, version = 1) @TypeConverters({MyConverters.class}) public abstract class AppDatabase extends RoomDatabase { public abstract UserDao userDao(); //沒有參數的抽象方法,返回值所表明的類必須用@Dao註解 }
Build後會自動生成的AppDatabase的實現類:
@Entity(tableName = "table_user") //自定義表名稱 public class User { @PrimaryKey(autoGenerate = true) public int uid;//自增主鍵 @ColumnInfo(name = "user_name") public String name; //自定義列名稱 public Date birthday; //類型轉換 @Ignore public String tips = "忽略的字段"; //忽略 //省略了get、set、構造方法等代碼 }
@Dao public interface UserDao { @Insert long insertUser(User user); //參數至少有一個,有一個時能夠返回long(表明新插入item的rowId)或不返回 @Insert List<Long> insertAll(User... users); //參數也能夠是集合或者數組,此時能夠返回long[]或者List<Long>或不返回 @Insert(onConflict = OnConflictStrategy.REPLACE) void insertAll(List<User> users);//能夠執行事務操做 @Delete int delete(User user);//根據主鍵刪除entities,能夠沒有返回值或者返回int,返回int值時表示刪除的數量 @Delete int deleteAll(List<User> users); //參數至少有一個,也能夠是集合或者數組,參數必須是使用@Entity註解標註的類,且不能爲null @Query("SELECT * FROM table_user") List<User> getAll();//返回值爲集合或數組的話,返回找到的全部數據,沒找到時返回長度爲0的集合或數組 @Query("SELECT * FROM table_user WHERE user_name LIKE :name LIMIT 1") User findUserByName(String name); //返回值爲User的話,只返回找到的第一條數據,沒找到時返回null @Query("SELECT * FROM table_user WHERE uid IN (:userIds)") User[] loadAllByIds(int[] userIds); //沒找到時返回長度爲0的集合或數組 @Query("SELECT * FROM table_user WHERE uid > :uid LIMIT 5") Cursor biggerId(int uid);//不推薦 @Query("SELECT * from table_user where uid = :uid") Flowable<User> findUserById(int uid); //和rxjava結合使用 @Query("SELECT * from table_user where user_name LIKE :name ") LiveData<List<User>> loadUsersByName(String name); //和LiveData結合使用 @Query("SELECT * from table_user where user_name LIKE '_%' || :name || '%'") LiveData<List<User>> loadUsers(String name);//模糊搜索 }
Build後自動生成的UserDao的實現類:
public class MainActivity extends FragmentActivity { private TextView tvTitle, tvWeixin, tvFriend, tvContact, tvSetting; private String[] titles = {"微信", "通信錄", "發現", "我"}; private MyViewModel mModel; private AppDatabase db; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); mModel = ViewModelProviders.of(this).get(MyViewModel.class); db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "dbname").build(); db.userDao().loadUsersByName("%哎_%") //返回LiveData<List<User>>,實際數據庫操做工做在子線程 .observe(this, users -> { Log.i("bqt", "【是否在主線程中】" + (Looper.getMainLooper() == Looper.myLooper())); //true String newUsers = GsonUtils.toJson(users); Log.i("bqt", "【數據庫中查詢到的數據發生變化】" + newUsers); mModel.getMutableLiveData().setValue(newUsers); //這裏是主線程,能夠直接使用setValue }); } private void initView() { tvWeixin = findViewById(R.id.tv_tab_bottom_weixin); tvFriend = findViewById(R.id.tv_tab_bottom_friend); tvContact = findViewById(R.id.tv_tab_bottom_contact); tvSetting = findViewById(R.id.tv_tab_bottom_setting); tvWeixin.setTag(0); tvFriend.setTag(1); tvContact.setTag(2); tvSetting.setTag(3); tvWeixin.setOnClickListener(v -> onClickTab((Integer) v.getTag())); tvFriend.setOnClickListener(v -> onClickTab((Integer) v.getTag())); tvContact.setOnClickListener(v -> onClickTab((Integer) v.getTag())); tvSetting.setOnClickListener(v -> onClickTab((Integer) v.getTag())); tvTitle = findViewById(R.id.tv_title); tvTitle.setOnClickListener(v -> { new Thread(() -> { List<User> allUsers = db.userDao().getAll();//必須在子線程訪問數據庫 mModel.getMutableLiveData().postValue("所有數據\n" + GsonUtils.toJson(allUsers)); //在子線程必須用postValue }).start(); tvTitle.setBackgroundColor(0xFF000000 + new Random().nextInt(0xFFFFFF)); }); getSupportFragmentManager().beginTransaction().add(R.id.id_container, MyFragment.newInstance("MyFragment")).commit(); } private void onClickTab(int position) { tvWeixin.setSelected(position == 0); tvFriend.setSelected(position == 1); tvContact.setSelected(position == 2); tvSetting.setSelected(position == 3); tvTitle.setText(titles[position]); switch (position) { case 0: new Thread(() -> { long id = db.userDao().insertUser(new User("哎")); List<Long> ids = db.userDao().insertAll(new User("哎哎" + new Random().nextInt(100))); db.userDao().insertAll(Arrays.asList(new User("哎啊"), new User("啊哎"))); Log.i("bqt", "【id】" + id + "【ids】" + ids); }).start(); //不能在主線程訪問數據庫,不然報IllegalStateException break; case 1: new Thread(() -> { User user = db.userDao().findUserByName("啊哎"); //若是沒找到的話返回null if (user != null) Log.i("bqt", "【delete數量】" + db.userDao().delete(user)); //不能傳入空,不然崩潰 User[] users = db.userDao().loadAllByIds(new int[]{1, 2, 3, 4, 5, 6}); //沒找到時返回長度爲0的集合或數組 Log.i("bqt", "【deleteAll數量】" + db.userDao().deleteAll(Arrays.asList(users))); }).start(); break; case 2: new Thread(() -> { User user = db.userDao().findUserByName("哎%"); //若是沒找到的話返回null user.name = "更新name"; Log.i("bqt", "【updateAll數量】" + db.userDao().updateAll(user)); }).start(); break; case 3: db.userDao().findUserById(1) //返回Flowable<User> .map(GsonUtils::toJson) //數組類型轉換 .subscribeOn(Schedulers.io()) //必須切換到子線程中訪問數據庫 //.observeOn(AndroidSchedulers.mainThread()) //能夠在子線程中使用postValue,也能夠切換到主線程中使用setValue .subscribe(s -> mModel.getMutableLiveData().postValue(s)); default: break; } } }
2019-3-29