Google官方Android的Room持久化庫示例Demo


是時候該放棄 GreenDao的使用了,該使用Room持久化庫嘍~~~

理由:GreenDao庫已經很少維護更新了,greenDao現在在較新的開發環境使用中會警告:

WARNING: API 'variant.getJavaCompiler()' is obsolete and has been replaced with 'variant.getJavaCompileProvider()'. It will be removed at the end of 2019.

百度的解決方法:

解決方法: 將gradle 從高版本還原到 3.2以下 

 


 

文檔地址:https://developer.android.com/topic/libraries/architecture/room.html

Room 在 SQLite 上提供了一個抽象層,以便在充分利用 SQLite 的強大功能的同時,能夠流暢地訪問數據庫。

處理大量結構化數據的應用可極大地受益於在本地保留這些數據。最常見的用例是緩存相關數據。這樣,當設備無法訪問網絡時,用戶仍可在離線狀態下瀏覽相應內容。設備之后重新連接到網絡后,用戶發起的所有內容更改都會同步到服務器。

由於 Room 負責為您處理這些問題,因此強烈建議您使用 Room(而不是 SQLite)。

注意:要在應用中使用 Room,請在應用的 build.gradle 文件中聲明 Room 依賴項

dependencies { def room_version = "2.2.0-rc01" implementation "androidx.room:room-runtime:$room_version" annotationProcessor "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor // optional - Kotlin Extensions and Coroutines support for Room
      implementation "androidx.room:room-ktx:$room_version"

      // optional - RxJava support for Room
      implementation "androidx.room:room-rxjava2:$room_version"

      // optional - Guava support for Room, including Optional and ListenableFuture
      implementation "androidx.room:room-guava:$room_version"

      // Test helpers
      testImplementation "androidx.room:room-testing:$room_version" }

 

Room 包含 3 個主要組件:

  • 數據庫:包含數據庫持有者,並作為應用已保留的持久關系型數據的底層連接的主要接入點。

    使用 @Database 注釋的類應滿足以下條件:

    • 是擴展 RoomDatabase 的抽象類。
    • 在注釋中添加與數據庫關聯的實體列表。
    • 包含具有 0 個參數且返回使用 @Dao 注釋的類的抽象方法。

    在運行時,您可以通過調用 Room.databaseBuilder()Room.inMemoryDatabaseBuilder() 獲取 Database 的實例。

  • Entity:表示數據庫中的表。

  • DAO:包含用於訪問數據庫的方法。

應用使用 Room 數據庫來獲取與該數據庫關聯的數據訪問對象 (DAO)。然后,應用使用每個 DAO 從數據庫中獲取實體,然后再將對這些實體的所有更改保存回數據庫中。最后,應用使用實體來獲取和設置與數據庫中的表列相對應的值。

Room 不同組件之間的關系如圖 所示:

創建實體和Dao:

package com.loaderman.roomdemo.bean; import androidx.annotation.NonNull; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.Ignore; import androidx.room.Index; import androidx.room.PrimaryKey; //Entity: 表示數據庫內的表.
@Entity(tableName = "user", indices = {@Index(value = {"name"}, unique = true)})//有些時候, 數據庫中的某些域或幾組域必須是唯一的. 你可以通過將注解@Index的unique屬性設置為true, 強制完成唯一的屬性.
public class User { @PrimaryKey(autoGenerate = true)//主鍵是否自動增長,默認為false
    private int id; @NonNull// 表示參數,成員變量或者方法返回值從不為null
    @ColumnInfo(name = "name")//指定數據庫表的列名。
    private String name; //演示版本2,升級數據庫版本用, 演示版本1注釋掉即可
    private String phone; private int age; public User() { } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Ignore public User(String name, int age) { this.name = name; this.age = age; } @Ignore public User(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } // getter setter方法必須寫
    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; } @Override public String toString() { return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", phone='" + phone + '\'' +
                ", age=" + age +
                '}'; } }
package com.loaderman.roomdemo.bean; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.ForeignKey; import androidx.room.PrimaryKey; //@ForeignKey注解定義它和實體User的關系 /* * 外鍵非常強大, 因為它允許你指定做什么操作, 在引用實體更新的時候. 比如, 你可以告訴SQLite為用戶刪除所有的書, * 在相應的User實例被刪除時, 而該User被Book通過在@ForeignKey注解里面聲明onDelete = CASCADE而關聯. * 備注: SQLite將@Insert(onConflict = REPLACE)作為REMOVE和REPLACE的集合來操作, 而非單獨的UPDATE操作. 這個取代沖突值的方法能夠影響你的外鍵約束. * */ @Entity(foreignKeys = @ForeignKey(entity = User.class, parentColumns = "id", childColumns = "user_id")) public class Book { @PrimaryKey public int bookId; public String title; @ColumnInfo(name = "user_id") public int userId; }
package com.loaderman.roomdemo.dao; import androidx.lifecycle.LiveData; import androidx.room.Dao; import androidx.room.Delete; import androidx.room.Insert; import androidx.room.OnConflictStrategy; import androidx.room.Query; import androidx.room.Update; import com.loaderman.roomdemo.bean.Book; import com.loaderman.roomdemo.bean.User; import java.util.List; import io.reactivex.Flowable; //DAO: 包含用於訪問數據庫的方法.
@Dao public interface UserDao { // OnConflictStrategy.REPLACE表示如果已經有數據,那么就覆蓋掉 //數據的判斷通過主鍵進行匹配,也就是uid,非整個user對象 //返回Long數據表示,插入條目的主鍵值(id)
 @Query("SELECT * FROM user") List<User> getAllUsers(); // LiveData是可以被觀察到的數據持有類。它里面緩存或持有了最新的數據。當數據改變時會通知它的觀察者。 // LiveData是可以感知生命周期的。UI組件只是觀察相關數據,不會停止或恢復觀察。 // LiveData自動管理所有這些,因為它在觀察時意識到相關的生命周期狀態變化。
    @Query("SELECT * FROM user") LiveData<List<User>> getAllUser(); @Query("SELECT * FROM user WHERE id=:id") User getUser(int id); @Query("SELECT * FROM user WHERE name=:name") User getUser(String name); @Insert(onConflict = OnConflictStrategy.REPLACE) List<Long> insert(User... users); @Insert(onConflict = OnConflictStrategy.REPLACE) Long insert(User user); @Insert(onConflict = OnConflictStrategy.REPLACE) List<Long> insert(List<User> userLists); @Update int update(User... users); @Update() int updateAll(User... user); @Update() int updateAll(List<User> user); @Delete int delete(User user); @Delete int deleteAll(List<User> users); @Delete int deleteAll(User... users); //返回RxJava2中的Publisher和Flowable.
    @Query("SELECT * from user where name = :name LIMIT 1") Flowable<User> getUserByName(String name); //多表查詢
    @Query("SELECT * FROM book "
            + "INNER JOIN user ON user.id = book.user_id "
            + "WHERE   user.id = :userName") List<Book> findBooksByUserName(String userName); }

AppDatabase.java

package com.loaderman.roomdemo.dao; import android.content.Context; import androidx.room.Database; import androidx.room.Room; import androidx.room.RoomDatabase; import androidx.room.TypeConverters; import androidx.room.migration.Migration; import androidx.sqlite.db.SupportSQLiteDatabase; import com.loaderman.roomdemo.Converters; import com.loaderman.roomdemo.bean.Book; import com.loaderman.roomdemo.bean.User; //注解指定了database的表映射實體數據以及版本等信息 推薦使用單例模式
@Database(entities = { User.class, Book.class }, version = 2,exportSchema = true) //添加@TypeConverters注解到AppDatabase類上, 之后Room就能夠在AppDatabase中定義的每一個實體和DAO上使用這個轉換器.
@TypeConverters({Converters.class}) public abstract class AppDatabase extends RoomDatabase { public static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL("ALTER TABLE user ADD COLUMN phone TEXT "); } }; public abstract UserDao getUserDao(); }

潛逃對象的創建示例:

package com.loaderman.roomdemo.bean; import androidx.room.ColumnInfo; import androidx.room.Embedded; import androidx.room.Entity; import androidx.room.PrimaryKey; /* 創建嵌套對象有些時候, 在數據庫邏輯中, 你想將一個實體或者POJO表示為一個緊密聯系的整體, 即使這個對象包含幾個域. 在這些情況下, 你能夠使用@Embedded注解來表示一個對象, 而你想將這個對象分解為表內的子域. 然后你可以查詢這些嵌套域, 就像你查詢其它的獨立列一樣. 這個表表示UserInfo對象包含如下幾列: id, firstName, street, state, city和post_code. 備注: 嵌套的域同樣可以包含其它的嵌套域. 如果實體擁有多個相同類型的嵌套域, 你可以通過設置prefix屬性保留每一列唯一. 然后Room給嵌套對象的每一個列名的起始處添加prefix設置的給定值 */ @Entity public class UserInfo { @PrimaryKey public int id; public String firstName; @Embedded public Address address; } class Address { public String street; public String state; public String city; @ColumnInfo(name = "post_code") public int postCode; }

類型轉換器

package com.loaderman.roomdemo; import androidx.room.TypeConverter; import java.sql.Date; //類型轉換器
public class Converters { @TypeConverter public static Date revertDate(long value) { return new Date(value); } @TypeConverter public static long converterDate(Date value) { return value.getTime(); } }

數據庫遷移

你添加和更改App功能時,你需要修改實體類來反映這些更改。當用戶更新到你的應用最新版本時,你不想要他們丟失所有存在的數據,尤其是你無法從遠端服務器恢復數據時。

Room允許你編寫Migration類來保留用戶數據。每個Migration類指明一個startVersionendVersion。在運行時,Room運行每個Migration類的migrate()方法,使用正確的順序來遷移數據庫到最新版本。

警告:如果你沒有提供需要的遷移類,Room將會重建數據庫,也就意味着你會丟掉數據庫中的所有數據。
警告:為了使遷移邏輯正常運行,請使用完整查詢,而不是引用代表查詢的常量。
示例代碼:
  public static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL("ALTER TABLE user ADD COLUMN phone TEXT "); } };

配置數據庫:

package com.loaderman.roomdemo; import android.app.Application; import androidx.room.Room; import com.loaderman.roomdemo.dao.AppDatabase; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); initDb(); } private static AppDatabase db; private void initDb() { db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "UserDB") //添加數據庫的變動遷移支持(當前狀態從version1到version2的變動處理) //數據庫升級1-->2
 .addMigrations(AppDatabase.MIGRATION_1_2) //下面注釋表示允許主線程進行數據庫操作,但是不推薦這樣做。 //他可能造成主線程lock以及anr // .allowMainThreadQueries()
 .build(); } public static AppDatabase getAppDbHelper() { return db; } }

記錄數據庫

在主module的build.gradle里面添加代碼即可

 defaultConfig { ... javaCompileOptions { annotationProcessorOptions { //room的數據庫概要、記錄
            arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] } } } sourceSets { //數據庫概要、記錄存放位置
    androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) }

效果:

 

 

測試:

 

package com.loaderman.roomdemo; import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.Observer; import android.os.Bundle; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.loaderman.roomdemo.bean.User; import com.loaderman.roomdemo.dao.UserDao; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import java.util.ArrayList; import java.util.List; import io.reactivex.Flowable; import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; public class MainActivity extends AppCompatActivity { UserDao userDao; private TextView tvMsg; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); userDao = MyApplication.getAppDbHelper().getUserDao(); tvMsg = findViewById(R.id.tv_msg); } public void onClick(View view) { switch (view.getId()) { case R.id.btn_insert_one: System.out.println("---------btn_insert_one----------------"); new Thread(new Runnable() { @Override public void run() { long insert = userDao.insert(new User(33,"張三", 18)); System.out.println(insert+"loaderman"); System.out.println("-------------------------"); MainActivity.this.setMsg("插入的主鍵值:" + insert); } }).start();; break; case R.id.btn_insert_multiple: new Thread(new Runnable() { @Override public void run() { List<User> list = new ArrayList<>(); list.add(new User(44,"李四", 42)); list.add(new User(55,"王五", 56)); List<Long> insertALl = userDao.insert(list); MainActivity.this.setMsg("插入的主鍵值:" + insertALl.toString()); } }).start();; break; case R.id.btn_query_one: new Thread(new Runnable() { @Override public void run() { User user1 = userDao.getUser("張三"); if (user1==null){ MainActivity.this.setMsg("沒有查詢到" ); }else { MainActivity.this.setMsg("查詢單個:" + user1.toString()); } } }).start();; break; case R.id.btn_query_multiple: new Thread(new Runnable() { @Override public void run() { List<User> allUsers = userDao.getAllUsers(); if (allUsers==null){ MainActivity.this.setMsg("沒有查詢到" ); }else { MainActivity.this.setMsg("查詢多個:" + allUsers.toString()); } } }).start();; break; case R.id.btn_update_one: new Thread(new Runnable() { @Override public void run() { int update = userDao.update(new User(33,"張三", 25)); MainActivity.this.setMsg("更新行數:" + update); } }).start(); break; case R.id.btn_update_multiple: new Thread(new Runnable() { @Override public void run() { List<User> list3 = new ArrayList<>(); list3.add(new User("李四", 44)); list3.add(new User("王五", 55)); int updateAll = userDao.updateAll(list3); MainActivity.this.setMsg("更新行數:" + updateAll); } }).start();; break; case R.id.btn_delete_one: new Thread(new Runnable() { @Override public void run() { int delete = userDao.delete(new User(33,"張三", 18)); MainActivity.this.setMsg("刪除行數:" + delete); } }).start();; break; case R.id.btn_delete_multiple: new Thread(new Runnable() { @Override public void run() { List<User> list2 = new ArrayList<>(); list2.add(new User(44,"李四", 42)); list2.add(new User(55,"王五", 56)); int deleteAll = userDao.deleteAll(list2); MainActivity.this.setMsg("刪除行數:" + deleteAll); } }).start();; break; case R.id.btn_rx_query: userDao.getUserByName("張三").observeOn(Schedulers.newThread()).subscribe(new Consumer<User>() { @Override public void accept(User user) { //該回調是子線程。需要在主線程刷新UI
                         MainActivity.this.setMsg("RxJava查詢:" + user.toString()); } }); break; case R.id.btn_live_query: userDao.getAllUser().observe(this, new Observer<List<User>>() { @Override public void onChanged(List<User> users) { Toast.makeText(MainActivity.this,users.toString(),Toast.LENGTH_LONG).show(); tvMsg.setText(users.toString()); } }); break; } } private void setMsg(final String msg) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this,msg,Toast.LENGTH_LONG).show(); tvMsg.setText(msg); } }); } }

 

 

 


多版本遷移

要是用戶剛下載的 APP,想升級到版本最新版本呢?目前我們定義了migrations:version 1 到 2, version 2 到 3, version 3 到 4, 所以 Room 會一個接一個的觸發所有 migration。實際開發過程中,一般不會那么頻繁


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM