1. 程式人生 > >Android 資料庫Realm入門

Android 資料庫Realm入門

之前使用本地資料庫都是用sqllite的,偶爾用一下LitePal,聽人說Realm多麼多麼好使,但是我一直都沒有去學習,今天有空去瞅了瞅,果然很不錯。

它有以下幾種特點: 1.易用:Ream 不是在SQLite基礎上的ORM,它有自己的資料查詢引擎。並且十分容易使用。 2.快速:由於它是完全重新開始開發的資料庫實現,所以它比任何的ORM速度都快很多,甚至比SLite速度都要快。 3.跨平臺:Realm 支援 iOS & OS X (Objective‑C & Swift) & Android。我們可以在這些平臺上共享Realm資料庫檔案,並且上層邏輯可以不用任何改動的情況下實現移植。 4.高階:Ream支援加密,格式化查詢,易於移植,支援JSON,流式api,資料變更通知等高階特性 5.視覺化

1.如何使用?

在開始配置之前你需要確保你滿足以下條件:

  • 目前不支援Android以外的Java
  • Android Studio >= 1.5.1
  • 較新的Android SDK版本
  • JDK version >=7.
  • API 9(Android 2.3)以及之後的版本

1.在專案的build檔案加上

classpath "io.realm:realm-gradle-plugin:4.3.1"

2.在app的build檔案加上

apply plugin: 'realm-android'

 3.在你的Application裡面初始化realm

public class MyApplication extends Application{
    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
        //預設配置
        RealmConfiguration configuration=new RealmConfiguration.Builder().build();
        Realm.setDefaultConfiguration(configuration);
    }
}

這時候會建立一個叫做 default.realm的Realm檔案,該檔案就是realm的資料庫檔案,一般來說,這個檔案位於/data/data/包名/files/。可以通過realm.getPath()來獲得該Realm的絕對路徑,可以通過.assetFile(this,"realm file path in assets")方法指定初始化的資料庫檔案。Realm會把制定路徑下的xxx.realm檔案copy到Context.getFilesDir()目錄中,以替換預設建立的空資料庫檔案。可以設定預設檔名,通過RealmConfiguration類進行配置。路徑似乎改不了,需要看具體裝置供應商的實現。

你也可以自定義設定

RealmConfiguration config = new RealmConfiguration.Builder()
                .name("myrealm.realm") //檔名 
                .schemaVersion(0) //版本號
                .build();
Realm realm = Realm.getInstance(config);

RealmConfiguration支援的方法:

  • Builder.name : 指定資料庫的名稱。如不指定預設名為default。
  • Builder.schemaVersion : 指定資料庫的版本號。
  • Builder.encryptionKey : 指定資料庫的金鑰。
  • Builder.migration : 指定遷移操作的遷移類。
  • Builder.deleteRealmIfMigrationNeeded : 宣告版本衝突時自動刪除原資料庫。
  • Builder.inMemory : 宣告資料庫只在記憶體中持久化。
  • build : 完成配置構建。

2.建立Model

建立一個Person類繼承RealmObject,在Realm中一個實體類對應一張表,實體類中所包含的屬性就是資料表的欄位

public class PersonBean extends RealmObject {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "PersonBean{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public PersonBean setName(String name) {
        this.name = name;
        return this;
    }

    public int getAge() {
        return age;
    }

    public PersonBean setAge(int age) {
        this.age = age;
        return this;
    }
}

 Realm幾乎支援所有的資料型別,需要注意的一點是整數型別 shortintlong 都被對映到 Realm 內的Long型別

它還有一些註解屬性

@PrimaryKey——表示該欄位是主鍵 使用過資料庫的同學應該看出來了,PrimaryKey就是主鍵。使用@PrimaryKey來標註,欄位型別必須是字串(String)或整數(byteshortintlong)以及它們的包裝型別(Byte,Short, Integer, 或 Long)。不可以存在多個主鍵,使用字串欄位作為主鍵意味著欄位被索引(註釋@PrimaryKey隱式地設定註釋@Index)。

重點:切記,Realm資料庫的主鍵欄位不是自動增長的,需要自己設定,做新增的時候如果不給id欄位值,預設會為0。後面再新增會報錯,說id為0的資料已經存在。尤其是批量新增的時候要注意,當心出現只添加了一條記錄的悲劇。

@Required——表示該欄位非空 在某些情況下,有一些屬性是不能為null的。使用@Required可用於用於強行要求其屬性不能為空,只能用於Boolean, Byte, Short, Integer, Long, Float, Double, String, byte[]Date。在其它型別屬性上使用 @Required修飾會導致編譯失敗。Tip:基本資料型別不需要使用註解 @Required,因為他們本身就不可為空。

@Ignore——表示忽略該欄位 被新增@Ignore標籤後,儲存資料時會忽略該欄位。

@Index——新增搜尋索引 為欄位新增搜尋索引,這樣會使得插入的速度變慢,資料量也變得更大。不過在查詢速度將變得更快,建議只在優化讀取效能的特定情況時新增索引。支援索引:StringbyteshortintlongbooleanDate欄位。

3.增刪改查

1.增

1.獲得Realm操作物件

 Realm realm=Realm.getDefaultInstance();

記得在介面銷燬時呼叫close方法

@Override 
protected void onDestroy() { 
    super.onDestroy();
    realm.close(); 
}

 做資料操作時要使用事務,你可以使用beginTransaction()來開始事務,使用commitTransaction()來提交事務

realm.beginTransaction();
        for (int i=0;i<10;i++){
            PersonBean personBean=realm.createObject(PersonBean.class);
            personBean.setAge(i).setName("流月");
        }
        realm.commitTransaction();

 你也可以使用executeTransaction()來執行事務程式碼塊

realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                PersonBean personBean = realm.createObject(PersonBean.class);
                personBean.setAge(66).setName("流月");
            }
        });

 你還可以使用copyToRealmOrUpdatecopyToRealm方法插入資料

當Model中存在主鍵的時候,推薦使用copyToRealmOrUpdate方法插入資料。如果物件存在,就更新該物件;反之,它會建立一個新的物件。若該Model沒有主鍵,使用copyToRealm方法,否則將丟擲異常。

realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                realm.copyToRealmOrUpdate(new PersonBean().setAge(66).setName("流月"));
            }
        });

注意:如果在UI執行緒中插入過多的資料,可能會導致主執行緒擁塞。

所以你可以使用executeTransactionAsync,該方法會開啟一個子執行緒來執行事務,並且在執行完成後進行結果通知。

 RealmAsyncTask transaction = realm.executeTransactionAsync(new Realm.Transaction() {
                   @Override
                   public void execute(Realm realm) {
                       PersonBean personBean = realm.createObject(PersonBean.class);
                       personBean.setAge(66).setName("流月");
                   }
               }, new Realm.Transaction.OnSuccess() {
                   @Override
                   public void onSuccess() {
                       //成功回撥 
                   }
               },
                new Realm.Transaction.OnError() {
                    @Override
                    public void onError(Throwable error) { 
                        //失敗回撥 
                    }
                });

注意:如果當AcitivityFragment被銷燬時,在OnSuccessOnError中執行UI操作,將導致程式奔潰 。用RealmAsyncTask .cancel();可以取消事務onStop中呼叫,避免crash

public void onStop () {
    if (transaction != null && !transaction.isCancelled()) {
        transaction.cancel();
      }
}

Realm還支援JSON操作,它可以從JSON中建立物件,官方例子:

  // 一個city model public 
    class City extends RealmObject {
        private String city; 
        private int id; 
        // getters and setters left out ... 
    } 
    // 使用Json字串插入資料 
    realm.executeTransaction(new Realm.Transaction() {
        @Override public void execute(Realm realm) { 
            realm.createObjectFromJson(City.class, "{ city: \"Copenhagen\", id: 1 }"); 
        } 
    }); 
    // 使用InputStream插入資料 
    realm.executeTransaction(new Realm.Transaction() { 
        @Override public void execute(Realm realm) { 
            try { 
                InputStream is = new FileInputStream(new File("path_to_file")); 
                realm.createAllFromJson(City.class, is); 
            } catch (IOException e) { 
                throw new RuntimeException(); 
            } 
        } 
    });

Realm 解析 JSON 時遵循如下規則:

  • 使用包含空值(null)的 JSON 建立物件:
  • 對於非必須(可為空值的屬性),設定其值為 null;
  • 對於必須(不可為空值的屬性),丟擲異常;
  • 使用包含空值(null)的 JSON 更新物件:
  • 對於非必須(可為空值的屬性),設定其值為 null;
  • 對於必須(不可為空值的屬性),丟擲異常;
  • 使用不包含對應屬性的 JSON: * 該屬性保持不變

2.查

查詢所有

public List<PersonBean> queryAll() {        
        Realm  mRealm=Realm.getDefaultInstance();
    
        RealmResults<PersonBean> PersonBeans= mRealm.where(PeronBean.class).findAll();
        
        return mRealm.copyFromRealm(dogs);
}

注意RealmResults雖然實現了List介面,不過有很多方法是不能用的。比如addaddAllremoveclear等,呼叫後會直接拋異常。不過也不用當心誤用這些方法,因為它們都被標記為@Deprecated了。 

按條件查詢

 RealmResults<PersonBean> personBeans = realm.where(PersonBean.class).lessThan("age", 5).findAll();

 realm支援鏈式的查詢方法

  • equalTo ():條件查詢
  • sum():對指定欄位求和。
  • average():對指定欄位求平均值。
  • min(): 對指定欄位求最小值。
  • max() : 對指定欄位求最大值。count : 求結果集的記錄數量。
  • findAll(): 返回結果集所有欄位,返回值為RealmResults佇列
  • findAllSorted() : 排序返回結果集所有欄位,返回值為RealmResults佇列
  • between(), greaterThan(),lessThan(), greaterThanOrEqualTo() & lessThanOrEqualTo()
  • equalTo() & notEqualTo()
  • contains(), beginsWith() & endsWith()
  • isNull() & isNotNull()
  • isEmpty()& isNotEmpty()

RealmQuery以及or的使用 在使用where()方法時,能得到一個RealmQuery物件,使用方法如下:

RealmResults<PersonBean> personBeanList = mRealm.where(PersonBean.class)
            .equalTo("name", "流月")
            .or().equalTo("name", "幽藍")
            .findAll();

排序

RealmResults<PersonBean> personBeanRealmResults = realm.where(PersonBean.class) .findAll();
personBeanRealmResults = personBeanRealmResults.sort("age"); //根據age,正序排列
personBeanRealmResults = personBeanRealmResults.sort("age", Sort.DESCENDING);//逆序排列

統計

 RealmResults<PersonBean> results = realm.where(PersonBean.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();

3.改

先查再改

public void update(View view) {
        RealmResults<PersonBean> personBeans = realm.where(PersonBean.class).lessThan("age", 5).findAll();
        realm.beginTransaction();
        for (PersonBean personBean : personBeans) {
            personBean.setAge(66);
        }
        realm.commitTransaction();
    }

4.刪

先查再刪

public void delete(View view) {
        realm.beginTransaction();
        RealmResults<PersonBean> personBeans = realm.where(PersonBean.class).equalTo("age", 8).findAll();
        personBeans.deleteAllFromRealm();
        realm.commitTransaction();
    }

刪除有四種方式

results.deleteFirstFromRealm(); //刪除user表的第一條資料 
results.deleteLastFromRealm();//刪除user表的最後一條資料
results.deleteFromRealm(index);//刪除指定位置的資料
results.deleteAllFromRealm();//刪除user表的全部資料

4.注意事項

1、Realm儲存的結果其實是在一個檔案裡面,預設的檔名是"default.realm",在"Context.getFilesDir()"目錄中,即:/data/data//files/default.realm。意思是,當你在應用管理裡面給當前app"清除資料",realm資料庫的資料會丟失。故我們需要把預設的資料檔案放到asset目錄中,當資料庫初始化時再copy到"Context.getFilesDir()"下。

2、Realm的例項需要在每次的具體操作中獲取,可以看成是一個數據操作的sessin,用完後必須close關閉。

3、建立物件和操作物件必須在同一個執行緒.違反了這條會報錯:java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.比如我們在UI執行緒查詢出來的物件, 想要非同步地刪除或者更新, 我們必須在新的執行緒重新查詢.

4、沒有主鍵自增的功能, 需要自己控制主鍵自增.

5、從List中刪除了一項之後, 最後的一項會移動過來補到被刪除的那一項原來的位置. 這是因為人家就是這麼設計的stackoverflow. 預設情況下是沒有排序的, 資料按照新增的順序返回, 但是這並不是一種保證, 所以當刪除了中間的元素, 後面的會補上這個位置, 以保證底層的資料是放在一起的. 解決辦法就是指定一個排序規則.

6、查詢出來的物件不可以臨時改變其資料, 否則會報錯:java.lang.IllegalStateException: Changing Realm data can only be done from inside a transaction.

7、不支援級聯刪除. 即從資料庫中刪除一個物件的時候, 不會刪除其中RealmObject子類或RealmList型別的欄位在資料庫中對應的資料. 這點也可以理解, 因為model之間的關係可能是多對多的. 所以需要實現級聯刪除的地方需要手動處理.