Realm 使用說明

AllW23 8年前發布 | 13K 次閱讀 Realm 數據庫服務器

Realm

一個跨平臺移動數據庫引擎

導入

  • 在項目的 build.gradle 添加:

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath "io.realm:realm-gradle-plugin:2.1.1"
        }
    }
  • 在模組的 build.gradle 中添加:

    apply plugin: 'realm-android'
  • ProGuard

    不需要針對 Realm 做 ProGuard 的改動

基本用法

模型

繼承 RealmObject ,或者實現 RealmModel 接口并添加注解 @RealmClass 。

public class User extends RealmObject {
    // 主鍵,可為空,默認已索引,String、byte、short、int、long、Byte、Short、Integer、Long
    @PrimaryKey
    private long id;
    // 非空,Boolean、ByteShort、Integer、Long、Float、Double、String、byte[]、Date
    @Required
    private String name;
    // 索引,String、byte、short、int、long、boolean、Date
    @Index
    private int age;
    @Ignore // 忽略
    private int tempReference;

private Dog dog; // 對單
private RealmList<Cat> cats; // 對多

// 省略 get/set 方法

}

public class Dog extends RealmObject { public String name; }

// 接口+注解,創建的托管對象缺少生成的部分方法,使用 RealmObject 的靜態方法替代 @RealmClass public class Cat implements RealmModel { public String name; }</code></pre>

初始化

// Application 中初始化
Realm.init(context);

Realm實例

Realm 實例是線程單例化的,也就是說多次在同一線程調用靜態構建器會返回同一 Realm 實例。

// Context.getFilesDir() 目錄下的 default.realm 文件
Realm realm = Realm.getDefaultInstance();

RealmConfiguration config = new RealmConfiguration.Builder().build(); // 默認的 RealmConfiguration Realm.setDefaultConfiguration(configuration); // 設置默認 RealmConfiguration

// 配合 Configuration 使用 Realm.deleteRealm(configuration); // 清除數據 Realm realm = Realm.getInstance(configuration); // 獲取自定義的 Realm</code></pre>

RealmConfiguration config = new RealmConfiguration.Builder()
        .name("myrealm.realm")  // 庫文件名
        .encryptionKey(getKey())  // 加密
        .schemaVersion(42)  // 版本號
        .modules(new MySchemaModule())  // 結構
        .migration(new MyMigration())  // 遷移
        .build();

// 非持久化的、存在于內存中的 Realm 實例 RealmConfiguration myConfig = new RealmConfiguration.Builder() .name("myrealm.realm") .inMemory() .build();</code></pre>

事務

所有的寫操作(添加、修改和刪除對象),必須包含在寫入事務中,確保線程安全。如果一個寫入事務正在進行,那么其他的線程的寫入事務就會阻塞它們所在的線程,使用異步事務以避免阻塞

讀取事務是隱式的,讀操作可在任何時候進行。當寫入事務被提交到 Realm 時,該 Realm 的所有其他實例都將被通知,讀入隱式事務將自動刷新你每個 Realm 對象。

realm.beginTransaction(); // 開始事務
realm.commitTransaction(); // 提交事務
realm.cancelTransaction(); // 取消事務

// 自動處理寫入事務的開始和提交,并在錯誤發生時取消寫入事務 realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // ... } });

// 異步事務,4種重載,onSuccess 和 onError 不是必須,非 Looper 線程中只有空(null)回調函數被允許使用 RealmAsyncTask transaction = realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm bgRealm) { // 異步不能使用外部的 Realm User user = bgRealm.createObject(User.class); user.setName("John"); user.setEmail("john@corporation.com"); } }, new Realm.Transaction.OnSuccess() { @Override public void onSuccess() { // 事務成功,Looper 傳回前臺執行 } }, new Realm.Transaction.OnError() { @Override public void onError(Throwable error) { // 事務失敗,自動取消,Looper 傳回前臺執行 } });

// 退出注意取消事務 public void onStop () { if (transaction != null && !transaction.isCancelled()) { transaction.cancel(); } }</code></pre>

添加

User realmUser = realm.createObject(User.class);

// 有主鍵需要添加主鍵,主鍵無自增 User realmUser = realm.createObject(User.class, primaryKeyValue);

// 普通對象轉化為托管對象,建議有主鍵的bean使用 User user = new User(); User realmUser = realm.copyToRealm(user); // 主鍵沖突時報異常 User realmUser = realm.copyToRealmOrUpdate(user); // 主鍵沖突時更新,無主鍵報異常</code></pre>

刪除

final RealmResults<User> results = realm.where(User.class).findAll();

realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // 刪除一個托管對象 results.get(0).deleteFromRealm(); // 使用以下方法,可避免自動更新集合前,某些元素有可能不在集合內,引起的崩潰 results.deleteFromRealm(0);

    // 刪除集合的首末對象
    results.deleteFirstFromRealm();
    results.deleteLastFromRealm();

    // 刪除所有集合內對象
    results.deleteAllFromRealm();

    // 刪除所有
    realm.delete(User.class);
    realm.deleteAll();
}

});</code></pre>

修改

直接修改托管對象,即修改了數據庫。

bean 有主鍵時,可使用 copyToRealmOrUpdate() 轉化相同主鍵的對象為托管來修改數據庫。

查詢

  • 查詢條件

    between()、greaterThan()、lessThan()、greaterThanOrEqualTo()、lessThanOrEqualTo()
    equalTo()、notEqualTo()
    contains()、beginsWith()、endsWith()
    isNull()、isNotNull()
    isEmpty()、isNotEmpty()
    RealmResults<User> result = realm.where(User.class)
            .between("age", 0, 99)
            .findAll(); // 執行查詢

    User user = realm.where(User.class) .equalTo("name", "John", Case.INSENSITIVE) // 忽略大小寫 .findFirst(); // 執行查詢</code></pre> </li>

  • 關聯查詢

    realmresults<user> users = realm.where(user.class)
           .equalto("dogs.name", "fluffy")  // 關聯查詢,以“.”分隔
           .equalto("dogs.color", "brown")  // 條件與
           .findall();

    realmresults<user> users = realm.where(user.class) .equalto("dogs.name", "fluffy") .findall() .where() // 在結果中繼續查詢 .equalto("dogs.color", "brown") .findall();</code></pre> </li>

  • 邏輯運算符

    or()、beginGroup()、endGroup()
    RealmResults<User> results = realm.where(User.class)
          .greaterThan("age", 10)  // 大于等于
          .beginGroup()  // 左括號
          .equalTo("name", "Peter")
          .or()  // 或,如果不加此操作符,默認為于
          .contains("name", "Jo")
          .endGroup()  // 左右括號
          .findAll();
  • 排序

    RealmResults<User> results = realm.where(User.class).findAll();
    results = result.sort("age"); // 升序
    results = result.sort("age", Sort.DESCENDING); // 降序

    RealmResults<User> results = realm.where(User.class) .findAllSorted("age", Sort.DESCENDING); // 降序</code></pre> </li>

  • 聚合

    RealmResults<User> results = realm.where(User.class).findAll();
    long   sum     = results.sum("age").longValue();
    long   min     = results.min("age").longValue();
    long   max     = results.max("age").longValue();
    double average = results.average("age");
    long   matches = results.size();
  • 異步

  • </ul>

    步查詢需要使用Handler來傳遞查詢結果。在沒有 Looper 的線程中使用異步查詢會導致 IllegalStateException 異常被拋出。

    Listener 只工作于 Looper 線程。對于非 Looper 線程請使用 Realm.waitForChange()

    private RealmResults<User> results;

    public void onStart() { realm = Realm.getDefaultInstance(); // 立刻返回一個 RealmResults<User>,當其完成時,RealmResults 實例會被更新 results = realm.where(User.class).findAllAsync();

      realm.addChangeListener(listener); // Realm 注冊監聽
      results.addChangeListener(listener); // 結果注冊監聽
    
      if (results.isLoaded()) {
        // 完成加載執行
      }
    
      results.load(); // 阻塞線程指導異步完成
    
    

    }

    public void onStop () { realm.removeChangeListener(listener); // Realm移除監聽 realm.removeAllChangeListeners(); // Realm移除所有監聽

    results.removeChangeListener(listener); // 結果移除監聽
    results.removeChangeListeners(); // 結果移除所有監聽
    

    }

    private RealmChangeListener listener = new RealmChangeListener<RealmResults<User>>() { @Override public void onChange(RealmResults<User> results) { // 在 Looper 線程,每次更新后執行 // 非 Looper 線程,使用 Realm.waitForChange() } };</code></pre>

    關閉

    Realm 實例是基于引用計數的, 調用 getInstance() 獲取了幾次實例,就需要調用 close() 關閉幾次

    UI 線程外的 Looper 線程

    public class MyThread extends Thread {

    private Realm realm;
    
    public void run() {
        Looper.prepare();
        try {
            realm = Realm.getDefaultInstance();
            //...
            Lopper.loop();
        } finally {
            if (realm != null) {
                realm.close();
            }
        }
    }
    

    }</code></pre>

    // AsyncTask
    protected Void doInBackground(Void... params) {
        Realm realm = Realm.getDefaultInstance();
        try {
            // ...
        } finally {
            realm.close();
        }

    return null;
    

    }</code></pre>

    new Thread(new Runnable() {
        @Override
        public void run() {
            Realm realm = null;
            try {
                realm = Realm.getDefaultInstance();
                // ...
            } finally {
                if (realm != null) {
                    realm.close();
                }
            }
        }
    }).start();

    注意

    • 基本數據類型永遠不能為空, RealmObject 數據類型永遠可以為空
    • 目前不支持 final 、 transient 和 volatile 修飾的成員變量
    • 支持使用遞歸關系,但要注意死循環的問題, Realm 不會檢查 RealmList 的循環嵌套
    • 設置一個類型為 RealmList 的屬性為空值(null)會清空該列表,即列表長度變為0。但并不會刪除列表中的任何 RealmObject
    • 在沒有 Looper 的線程中使用異步查詢會導致 IllegalStateException 異常被拋出。

    進階用法

    JSON

    JSON 包含空值(null)屬性,創建更新對象,對象屬性不可為空時拋出異常

    Json 和對象的屬性不同的,對象屬性不變

    realm.executeTransaction(new Realm.Transaction() {
        @Override
        public void execute(Realm realm) {
            Dog dog = realm.createObjectFromJson(Dog.class, "{\"name\": \"dog\"}");
        }
    });

    realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { try { InputStream is = getAssets().open("user.json"); realm.createAllFromJson(User.class, is); } catch (IOException e) { e.printStackTrace(); } } });</code></pre>

    DynamicRealm

    某些數據模型在編譯期是無法獲得的。例如在處理數據遷移(migration)或CSV文件的時候,此時使用 DynamicRealm 可以在沒有 RealmObject 子類的情況下操作 Realm 數據

    RealmConfiguration realmConfig = new RealmConfiguration.Builder().build();
    DynamicRealm realm = DynamicRealm.getInstance(realmConfig);

    // 創建 DynamicRealmObject 實例 DynamicRealmObject user = realm.createObject("User");

    // 通過字符串訪問數據,而不是 RealmObject 的定義 String name = person.getString("name"); int age = person.getInt("age");

    // DynamicRealm 會忽略 schema、migration 以及 schema 版本的檢查,但結構依然存在。獲取不存在的屬性會報異常 person.getString("I don't exist");

    // 查詢工作相同 RealmResults<DynamicRealmObject> users = realm.where("User") .equalTo("name", "John") .findAll();</code></pre>

    schema 結構

    • Realm 使用所有項目中的 Realm 模型類來創建 schema。但這個行為是可以改變的,例如,你可以通過使用 RealmModule 讓 Realm 只包含所有模型類的一個子集。

    @RealmModule(classes = { User.class, Dog.class })
    public class MyModule {
    }

    RealmConfiguration config = new RealmConfiguration.Builder() .modules(new MyModule()) // 設置使用的 schema .build();

    RealmConfiguration config = new RealmConfiguration.Builder() .modules(new MyModule(), new MyOtherModule()) // 可以設置多個 schema .build();</code></pre>

    • 在庫中使用到的 Realm 必須通過 RealmModule 來暴露和使用其 schema。

    // 庫必須使用 library = true,以阻止默認創建。
    // allClasses = true,即為使用所有
    @RealmModule(library = true, allClasses = true)
    public class MyLibraryModule {
    }

    // 庫需要確切的設置 RealmModule RealmConfiguration libraryConfig = new RealmConfiguration.Builder() .name("library.realm") .modules(new MyLibraryModule()) .build();

    // Apps 中添加庫的 RealmModule RealmConfiguration config = new RealmConfiguration.Builder() .name("app.realm") .modules(Realm.getDefaultModule(), new MyLibraryModule()) .build();</code></pre>

    數據庫升級

    • 不保存舊數據

      RealmConfiguration config = new RealmConfiguration.Builder()
          .deleteRealmIfMigrationNeeded()
          .build()
    • 數據遷移

      RealmConfiguration config = new RealmConfiguration.Builder()
          .schemaVersion(2)  // 結構改變時增加,默認初始值為0
          .migration(migration)  // 數據遷移方案
          .build()

      RealmMigration migration = new RealmMigration() { @Override public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {

       // 動態 Realm 獲取數據庫結構
       RealmSchema schema = realm.getSchema();
      
       // 遷移版本 1: 增加一個類
       // Example:
       // public User extends RealmObject {
       //     private String name;
       //     private int age;
       //     // getters and setters left out for brevity
       // }
       if (oldVersion == 0) {
          schema.create("User")
              .addField("name", String.class)
              .addField("age", int.class);
          oldVersion++;
       }
      
       // 遷移版本 2: 增加一個主鍵和對象引用
       // Example:
       // public Person extends RealmObject {
       //     @PrimaryKey
       //     private long id;
       //     private String name;
       //     private int age;
       //     private Dog favoriteDog;
       //     private RealmList<Dog> dogs;
       //     // getters and setters left out for brevity
       // }
       if (oldVersion == 1) {
          schema.get("User")
              .addField("id", long.class, FieldAttribute.PRIMARY_KEY)  // 增加主鍵熟悉“id”
              .addRealmObjectField("favoriteDog", schema.get("Dog"))  // 增加對象
              .addRealmListField("dogs", schema.get("Dog")); // 增加對象列表
          oldVersion++;
       }
      

      } }</code></pre> </li> </ul>

      加密

      Realm 文件可以通過傳遞一個512位(64字節)的密鑰參數給 Realm.getInstance().encryptionKey() 來加密存儲在磁盤上

      byte[] key = new byte[64];
      new SecureRandom().nextBytes(key);
      RealmConfiguration config = new RealmConfiguration.Builder()
        .encryptionKey(key)
        .build();

      Realm realm = Realm.getInstance(config);</code></pre>

      高階用法

      Android相關

      • Adapter

        ListView 使用 RealmBaseAdapter , RecyclerViews 使用 RealmRecyclerViewAdapter

        dependencies {
            compile 'io.realm:android-adapters:1.4.0'
        }
      • Intent

        RealmObject 不能通過 Intent 傳遞,可以通過傳遞屬性然后再查詢

      • AsyncTask

        private class DownloadOrders extends AsyncTask<Void, Void, Long> {
            @Override
            protected Long doInBackground(Void... voids) {
                // 后臺子線程,獲取使用并關閉 Realm
                Realm realm = Realm.getDefaultInstance();
                try {
                    realm.createAllFromJson(Order.class, api.getNewOrders());
                    Order firstOrder = realm.where(Order.class).findFirst();
                    long orderId = firstOrder.getId();
                    return orderId;
                } finally {
                    realm.close();
                }
            }

        @Override
        protected void onPostExecute(Long orderId) {
            // 返回主線程,通過id查詢對象,進行操作
        }
        

        }</code></pre> </li>

      • IntentService

        ChangeListener 在 IntentService 中不會工作。盡管 IntentService 本身是一個 Looper 線程,但每次 onHandleIntent 的調用是獨立的事件。你可以注冊監聽器的調用不會返回失敗,但他們永遠不會被觸發。

        public class OrdersIntentService extends IntentService {
            public OrdersIntentService(String name) {
                super("OrdersIntentService");
            }

        @Override
        protected void onHandleIntent(Intent intent) {
            // 后臺子線程,獲取使用并關閉 Realm
            Realm realm = Realm.getDefaultInstance();
            realm.createAllFromJson(Order.class, api.getNewOrders());
            Order firstOrder = realm.where(Order.class).findFirst();
            long orderId = firstOrder.getId();
            realm.close();
        }
        

        }</code></pre> </li> </ul>

        Retrofit

        GitHubService service = restAdapter.create(GitHubService.class);
        List<Repo> repos = service.listRepos("octocat");

        // Retrofit 獲取的對象轉換成 Realm 對象 realm.beginTransaction(); List<Repo> realmRepos = realm.copyToRealmOrUpdate(repos); realm.commitTransaction();</code></pre>

        RxJava

        Realm 、 RealmResults 、 RealmObject 、 DynamicRealm 、 DynamicRealmObject 可以轉化為 Observable

        Realm realm = Realm.getDefaultInstance();
        GitHubService api = retrofit.create(GitHubService.class);
        // 組合 Realm, Retrofit 和 RxJava (使用 Retrolambda),
        realm.where(Person.class)
                .isNotNull("username")
                .findAllAsync()
                .asObservable()  // 轉化為 Observable
                .filter(persons.isLoaded)
                .flatMap(persons -> Observable.from(persons))
                .flatMap(person -> api.user(person.getGithubUserName()))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(user -> showUser(user));

        Parceler

        compile "org.parceler:parceler-api:1.0.3"
        apt "org.parceler:parceler:1.0.3"
        // 項目編譯完成,RealmObject 轉化成 RealmProxy
        @Parcel(implementations = { UserRealmProxy.class },
                value = Parcel.Serialization.BEAN,
                analyze = { User.class })
        public class User extends RealmObject {
            // ...
        }
        • 如果你的模型包含 RealmList,那么你需要注冊一個特殊 adapter

        • 一旦對象被打包(parcelled),它將變為一個有當前數據快照,不再被 Realm 管理的一個 unmanaged 對象。之后該對象的數據變化不會被 Realm 寫入。

         

        來自:http://www.jianshu.com/p/95f62e739d1b

         

 本文由用戶 AllW23 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!